From 29ef9a7a35f306571d6e10d919271c5e1938f71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro=20Arvela?= Date: Mon, 4 Jan 2016 00:53:36 +0000 Subject: [PATCH 001/224] Add Portuguese translation, closes #106. Add translation provided by @smarquespt in #106. Translate additional missing strings added to the program since then. --- app/src/main/res/values-pt/arrays.xml | 29 +++++++ app/src/main/res/values-pt/plurals.xml | 32 +++++++ app/src/main/res/values-pt/strings.xml | 111 +++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 app/src/main/res/values-pt/arrays.xml create mode 100644 app/src/main/res/values-pt/plurals.xml create mode 100644 app/src/main/res/values-pt/strings.xml diff --git a/app/src/main/res/values-pt/arrays.xml b/app/src/main/res/values-pt/arrays.xml new file mode 100644 index 000000000..6817618bd --- /dev/null +++ b/app/src/main/res/values-pt/arrays.xml @@ -0,0 +1,29 @@ + + + + @string/light + @string/dark + @string/black + + + + claro + escuro + preto + + + + + @string/last_opened + @string/songs + @string/albums + @string/artists + + + + última opção + faixas + álbums + artistas + + \ No newline at end of file diff --git a/app/src/main/res/values-pt/plurals.xml b/app/src/main/res/values-pt/plurals.xml new file mode 100644 index 000000000..b5d2dd726 --- /dev/null +++ b/app/src/main/res/values-pt/plurals.xml @@ -0,0 +1,32 @@ + + + + + + @string/count_artist + @string/count_artists + + + + + @string/count_album + @string/count_albums + + + + @string/count_songs + @string/count_song + @string/count_songs + + + + %d faixa adicionada à fila. + %d faixas adicionadas à fila. + + + + %d faixa adicionada à lista de reprodução. + %d faixas adicionadas à lista de reprodução. + + + \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml new file mode 100644 index 000000000..ab0119bd0 --- /dev/null +++ b/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,111 @@ + + + + Timber + Definições + + Reproduzir + Pausa + Seguinte + Anterior + Baralhar + Baralhar tudo + Repetição + Repetir uma + Repetir todas + Abrir fila + Explorar músicas + + Ordenação + A-Z + Z-A + Artista + Álbum + Ano + N.º da faixa + Duração + Data de adição + Lista de faixas + Número de faixas + Número de álbuns + Nome de ficheiro + + Erro de reprodução + + Última adição + Reproduções recentes + Faixas favoritas + + %1$s | %2$s + + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + Reporte os erros encontrados aqui + Sugira funcionalidades na comunidade Google+ aqui + + Ver como + Lista + Grelha + + Claro + Escuro + Preto + + Última opção + + Faixas + Álbums + Artistas + Coleção + Listas de reprodução + Fila de reprodução + Em reprodução + Definições + Sobre + Ajuda e comentários + Pesquisa + Baralhar tudo + Equalizador + Pesquisar na coleção + %d faixa + %d faixas + %d álbum + %d álbuns + %d artista + %d artistas + Escolher Tema + Escolha o tema a utilizar na aplicação. + Escolha o ecrã a mostrar ao iniciar. + Ecrã inicial + Artistas em grelha + Exibir artistas em grelha no formato 2x2. + Utilizar animações do sistema na aplicação. + Animações do sistema + Utilizar animações de transição na aplicação. + Animações + Escolha o ecrã de reprodução de entre os 4 estilos disponíveis. + Personalização + Outras + O Timber é um reprodutor de músicas open source, desenvolvido por Naman Dwivedi. + Pausa na reprodução ao remover os auscultadores. + Pausa ao remover + Seleção atual + + + Tema Escuro + Aplicar um tema escuro à aplicação + + Cor Primária + Mudar a cor primária do tema + + Cor Secundária + Mudar a cor secundária do tema + + Colorir a barra de estado + Alternar a coloração da barra de estado + + Colorir a barra de navegação + Alternar a coloração da barra de navegação + + From f9c3debc40cb087b961cc3bdd8c2ff2718d21b4e Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 4 Jan 2016 17:01:36 +0530 Subject: [PATCH 002/224] clean up translatons --- app/src/main/res/values-de/arrays.xml | 29 -------------------- app/src/main/res/values-de/plurals.xml | 22 --------------- app/src/main/res/values-de/strings.xml | 14 ++++++---- app/src/main/res/values-fr/strings.xml | 14 ++++++---- app/src/main/res/values-it/strings.xml | 14 ++++++---- app/src/main/res/values-pt/arrays.xml | 29 -------------------- app/src/main/res/values-pt/plurals.xml | 32 ---------------------- app/src/main/res/values-pt/strings.xml | 26 +++++++++--------- app/src/main/res/values-zh/strings.xml | 14 ++++++---- app/src/main/res/values/arrays.xml | 3 ++ app/src/main/res/values/plurals.xml | 38 ++++++++++++++------------ app/src/main/res/values/strings.xml | 34 ++++++++++++++--------- 12 files changed, 89 insertions(+), 180 deletions(-) delete mode 100644 app/src/main/res/values-de/arrays.xml delete mode 100644 app/src/main/res/values-de/plurals.xml delete mode 100644 app/src/main/res/values-pt/arrays.xml delete mode 100644 app/src/main/res/values-pt/plurals.xml diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml deleted file mode 100644 index 19002b5e1..000000000 --- a/app/src/main/res/values-de/arrays.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - @string/light - @string/dark - @string/black - - - - light - dark - black - - - - - @string/last_opened - @string/songs - @string/albums - @string/artists - - - - last_opened - songs - albums - artists - - \ No newline at end of file diff --git a/app/src/main/res/values-de/plurals.xml b/app/src/main/res/values-de/plurals.xml deleted file mode 100644 index f1c5d5f80..000000000 --- a/app/src/main/res/values-de/plurals.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - @string/count_artist - @string/count_artists - - - - - @string/count_album - @string/count_albums - - - - @string/count_songs - @string/count_song - @string/count_songs - - - \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 80d210dd8..e6c677a12 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -62,12 +62,7 @@ Alles zufällig abspielen Equalizer Bibliothek durchsuchen - %d Song - %d Songs - %d Album - %d Alben - %d Interpret - %d Interpreten + Design auswählen Wählen Sie ein Design welches die App nutzt. Wählen Sie die Startseite die beim starten der App angezeigt wird. @@ -85,5 +80,12 @@ Wiedergabe pausieren wenn Kopfhörer entfernt werden. Pause beim entfernen + %d Song + %d Songs + %d Album + %d Alben + %d Interpret + %d Interpreten + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f27acaf15..6b186db90 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -62,12 +62,7 @@ Aléatoire Egaliseur Rechercher dans la bibliothèque - %d chanson - %d chansons - %d album - %d albums - %d artiste - %d artistes + Sélectionner le thème Choisissez le thème qui sera utilisé dans l\'application Choisissez l\'écran de départ au lancement de l\'application @@ -85,4 +80,11 @@ Mettre en pause la musique lorsque les écouteurs sont déconnectés Pause dès qu\'écouteurs déconnectés Sélection courante + + %d chanson + %d chansons + %d album + %d albums + %d artiste + %d artistes diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 25436bd9e..de7fb5ec9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -62,12 +62,7 @@ Brani casuali Equalizzatore Cerca nella libreria - %d canzone - %d canzoni - %d album - %d album - %d artista - %d artisti + Seleziona tema Scegli un tema da usare all\'interno dell\'app. Seleziona la pagina di apertura da mostrare all\'avvio. @@ -85,4 +80,11 @@ Abilita pausa della riproduzione quando vengono staccate le cuffie Pausa al distacco Attualmente selezionato + + %d canzone + %d canzoni + %d album + %d album + %d artista + %d artisti diff --git a/app/src/main/res/values-pt/arrays.xml b/app/src/main/res/values-pt/arrays.xml deleted file mode 100644 index 6817618bd..000000000 --- a/app/src/main/res/values-pt/arrays.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - @string/light - @string/dark - @string/black - - - - claro - escuro - preto - - - - - @string/last_opened - @string/songs - @string/albums - @string/artists - - - - última opção - faixas - álbums - artistas - - \ No newline at end of file diff --git a/app/src/main/res/values-pt/plurals.xml b/app/src/main/res/values-pt/plurals.xml deleted file mode 100644 index b5d2dd726..000000000 --- a/app/src/main/res/values-pt/plurals.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - @string/count_artist - @string/count_artists - - - - - @string/count_album - @string/count_albums - - - - @string/count_songs - @string/count_song - @string/count_songs - - - - %d faixa adicionada à fila. - %d faixas adicionadas à fila. - - - - %d faixa adicionada à lista de reprodução. - %d faixas adicionadas à lista de reprodução. - - - \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ab0119bd0..6f6dc5571 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,7 +1,6 @@ - Timber Definições Reproduzir @@ -36,11 +35,6 @@ Reproduções recentes Faixas favoritas - %1$s | %2$s - - %1$s %2$s - %2$d:%3$02d - %1$d:%2$02d:%3$02d Reporte os erros encontrados aqui Sugira funcionalidades na comunidade Google+ aqui @@ -68,12 +62,7 @@ Baralhar tudo Equalizador Pesquisar na coleção - %d faixa - %d faixas - %d álbum - %d álbuns - %d artista - %d artistas + Escolher Tema Escolha o tema a utilizar na aplicação. Escolha o ecrã a mostrar ao iniciar. @@ -92,7 +81,6 @@ Pausa ao remover Seleção atual - Tema Escuro Aplicar um tema escuro à aplicação @@ -108,4 +96,16 @@ Colorir a barra de navegação Alternar a coloração da barra de navegação + %d faixa + %d faixas + %d álbum + %d álbuns + %d artista + %d artistas + + %d faixa adicionada à fila. + %d faixas adicionadas à fila. + %d faixa adicionada à lista de reprodução. + %d faixas adicionadas à lista de reprodução. + diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 5bdab43aa..15a3a82c1 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -62,12 +62,7 @@ 随机 均衡 在库中搜索 - %d 首歌 - %d 首歌 - %d 张专辑 - %d 张专辑 - %d 位艺术家 - %d 位艺术家 + 选择主题 选择一个主题,在整个应用中。 选择起始页,在启动时显示。 @@ -103,4 +98,11 @@ 彩色状态栏 切换状态栏色彩 + %d 首歌 + %d 首歌 + %d 张专辑 + %d 张专辑 + %d 位艺术家 + %d 位艺术家 + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 19002b5e1..964a094a9 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,5 +1,8 @@ + + + @string/light @string/dark diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 99626e39e..51989945a 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -1,32 +1,34 @@ - + - - @string/count_artist - @string/count_artists - + + + + @string/count_artist + @string/count_artists + - - @string/count_album - @string/count_albums - + + @string/count_album + @string/count_albums + - - @string/count_songs - @string/count_song - @string/count_songs - + + @string/count_songs + @string/count_song + @string/count_songs + - %d song added to the queue. - %d songs added to the queue. + @string/number_song_add_queue + @string/number_songs_add_queue - %d song added to playlist. - %d songs added to playlist. + @string/number_song_add_playlist + @string/number_songs_add_playlist \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c54dee2fe..e056bb869 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,6 @@ - Timber Settings Play @@ -36,11 +35,6 @@ Recently Played My Top Tracks - %1$s | %2$s - - %1$s %2$s - %2$d:%3$02d - %1$d:%2$02d:%3$02d Report any bugs here Specify any feature request in the Google+ community here @@ -68,12 +62,7 @@ Shuffle All Equalizer Search Library - %d song - %d songs - %d album - %d albums - %d artist - %d artists + Select Theme Choose a theme to be used throughout the app. Choose the start page to be displayed on launch. @@ -92,7 +81,6 @@ Pause on Detach Currently Selected - Dark Theme Apply a dark theme throughout the app @@ -108,4 +96,24 @@ Colored Navigation Bar Toggle navigation bar coloring + %d song + %d songs + %d album + %d albums + %d artist + %d artists + + %d song added to the queue. + %d songs added to the queue. + %d song added to playlist. + %d songs added to playlist. + + + + Timber + %1$s | %2$s + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + From 377fd0a12e1292fd1afa7434f8145015c07bb3a1 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 4 Jan 2016 18:55:38 +0530 Subject: [PATCH 003/224] fix crash when changing now playing theme- resolves #116 --- .../nowplaying/BaseNowplayingFragment.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java b/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java index bd9f3743e..64ed80fe5 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java @@ -621,16 +621,20 @@ private class loadQueueSongs extends AsyncTask { @Override protected String doInBackground(String... params) { - mAdapter = new BaseQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity())); - return "Executed"; + if (getActivity() != null) { + mAdapter = new BaseQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity())); + return "Executed"; + } else return null; } @Override protected void onPostExecute(String result) { - recyclerView.setAdapter(mAdapter); - if (getActivity() != null) - recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); - recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 1); + if (result != null) { + recyclerView.setAdapter(mAdapter); + if (getActivity() != null) + recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); + recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 1); + } } From 771a6e32ba93cf20f08a25268d1da8d1a3f65e8e Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 4 Jan 2016 19:06:03 +0530 Subject: [PATCH 004/224] fix grid spacing on layout change- resolves #98 --- .../main/java/com/naman14/timber/fragments/AlbumFragment.java | 3 +++ .../main/java/com/naman14/timber/fragments/ArtistFragment.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/src/main/java/com/naman14/timber/fragments/AlbumFragment.java b/app/src/main/java/com/naman14/timber/fragments/AlbumFragment.java index 796ec43eb..64d89bc64 100644 --- a/app/src/main/java/com/naman14/timber/fragments/AlbumFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/AlbumFragment.java @@ -97,6 +97,7 @@ private void updateLayoutManager(int column) { recyclerView.setAdapter(new AlbumAdapter(getActivity(), AlbumLoader.getAllAlbums(getActivity()))); layoutManager.setSpanCount(column); layoutManager.requestLayout(); + setItemDecoration(); } private void reloadAdapter() { @@ -154,10 +155,12 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.menu_show_as_list: mPreferences.setAlbumsInGrid(false); + isGrid = false; updateLayoutManager(1); return true; case R.id.menu_show_as_grid: mPreferences.setAlbumsInGrid(true); + isGrid = true; updateLayoutManager(2); return true; } diff --git a/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java b/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java index 2acebf1a3..13044f507 100644 --- a/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java @@ -97,6 +97,7 @@ private void updateLayoutManager(int column) { recyclerView.setAdapter(new ArtistAdapter(getActivity(), ArtistLoader.getAllArtists(getActivity()))); layoutManager.setSpanCount(column); layoutManager.requestLayout(); + setItemDecoration(); } private void reloadAdapter() { @@ -150,10 +151,12 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.menu_show_as_list: mPreferences.setArtistsInGrid(false); + isGrid = false; updateLayoutManager(1); return true; case R.id.menu_show_as_grid: mPreferences.setArtistsInGrid(true); + isGrid = true; updateLayoutManager(2); return true; } From 0b1a8cb0f4d3a955f9cc03d6d0c8a66dcd270652 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 4 Jan 2016 19:19:40 +0530 Subject: [PATCH 005/224] fix incorrect palette bindings- resolves #97 --- .../com/naman14/timber/adapters/AlbumAdapter.java | 15 +++++++++++++++ .../naman14/timber/adapters/ArtistAdapter.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java b/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java index b88dcef2b..5060712b5 100644 --- a/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java @@ -25,13 +25,16 @@ import android.widget.ImageView; import android.widget.TextView; +import com.afollestad.appthemeengine.Config; import com.naman14.timber.R; import com.naman14.timber.models.Album; +import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -106,6 +109,18 @@ public void onGenerated(Palette palette) { } } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (isGrid) { + itemHolder.footer.setBackgroundColor(0); + if (mContext != null) { + int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext)); + itemHolder.title.setTextColor(textColorPrimary); + itemHolder.artist.setTextColor(textColorPrimary); + } + } + } }); if (TimberUtils.isLollipop()) diff --git a/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java b/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java index fa1d59b32..3a4d59b05 100644 --- a/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java @@ -27,18 +27,21 @@ import android.widget.ImageView; import android.widget.TextView; +import com.afollestad.appthemeengine.Config; import com.naman14.timber.R; import com.naman14.timber.lastfmapi.LastFmClient; import com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener; import com.naman14.timber.lastfmapi.models.ArtistQuery; import com.naman14.timber.lastfmapi.models.LastfmArtist; import com.naman14.timber.models.Artist; +import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.widgets.BubbleTextGetter; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -116,6 +119,18 @@ public void onGenerated(Palette palette) { } } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (isGrid) { + itemHolder.footer.setBackgroundColor(0); + if (mContext != null) { + int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext)); + itemHolder.name.setTextColor(textColorPrimary); + itemHolder.albums.setTextColor(textColorPrimary); + } + } + } }); } else { ImageLoader.getInstance().displayImage(artist.mArtwork.get(1).mUrl, itemHolder.artistImage, From e99a078cde7d23525236f3f3345ed8d382b3732d Mon Sep 17 00:00:00 2001 From: M2ck Date: Mon, 4 Jan 2016 15:37:11 +0100 Subject: [PATCH 006/224] Update french translation --- app/src/main/res/values-fr/strings.xml | 87 ++++++++++++++++---------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6b186db90..ee5eb5684 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,18 +1,18 @@ - Options + Paramètres - Lire + Lecture Pause Suivant Précédent Aléatoire - Aléatoire + Tout aléatoire Répéter Tout répéter - Répéter - Ouvrir la file d\'attente + Répéter une chanson + Ouvrir la file de lecture Parcourir la musique Trier par @@ -25,20 +25,20 @@ Durée Date d\'ajout Liste des pistes - Nombre de musiques + Nombre de chansons Nombre d\'albums Nom de fichier Erreur lors de la lecture de la piste Derniers ajouts - Ecoutes récentes - Top musiques + Écoutes récentes + Mes pistes préférées Signalez tout bug ici Proposez des fonctionnalités dans la communauté Google+ ici - Voir en tant que + Affichage Liste Grille @@ -46,40 +46,55 @@ Sombre Noir - Dernier ouverts + Derniers ouverts - Musiques + Chansons Albums Artistes - Librairie - Playlists - File d\'attente de lecture - En cours - Options - A propos - Aide - Recherche - Aléatoire - Egaliseur + Bibliothèque + Listes de lecture + File de lecture + En lecture + Paramètres + À propos + Aide et commentaires + Rechercher + Tout aléatoire + Égaliseur Rechercher dans la bibliothèque - Sélectionner le thème - Choisissez le thème qui sera utilisé dans l\'application - Choisissez l\'écran de départ au lancement de l\'application - Ecran de démarrage + Sélectionner un thème + Choisir un thème qui sera utilisé dans l\'interface + Choisir un écran d\'accueil qui sera affiché au lancement + Écran d\'accueil Artistes dans la grille - Afficher les articles dans une grille 2x2 - Activer les animations système + Afficher les artistes dans une grille 2x2 + Activer les animations système dans l\'interface Animations système - Activer les animations de transition + Activer les animations de transition dans l\'interface Animations - Choisissez un écran de lecture parmi les 4 proposés + Choisir un écran de lecture à partir de 4 styles différents Personnalisation Autre - Timber est une application développée par Naman Dwivedi. - Mettre en pause la musique lorsque les écouteurs sont déconnectés - Pause dès qu\'écouteurs déconnectés - Sélection courante + Timber est une application open source développée par Naman Dwivedi. + Mettre en pause la lecture lorsque le casque est débranché + Pause au débranchement + Actuellement sélectionné + + Thème sombre + Appliquer un thème sombre dans l\'interface + + Couleur principale + Changer la couleur principale du thème + + Couleur secondaire + Changer la couleur secondaire du thème + + Barre d\'état colorée + Activer la coloration de la barre d\'état + + Barre de navigation colorée + Activer la coloration de la barre de navigation %d chanson %d chansons @@ -87,4 +102,10 @@ %d albums %d artiste %d artistes + + %d chanson ajoutée à la file de lecture. + %d chansons ajoutées à la file de lecture. + %d chanson ajoutée à la liste de lecture. + %d chansons ajoutées à la liste de lecture. + From 79bbfc12848bd98de7c8cd1837b5e2aea53f9221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edison=20Iba=C3=B1ez?= Date: Mon, 4 Jan 2016 14:19:19 -0500 Subject: [PATCH 007/224] Add Spanish translation --- app/src/main/res/values-es/strings.xml | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 app/src/main/res/values-es/strings.xml diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 000000000..5748711d4 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,119 @@ + + + + Ajustes + + Reproducir + Pausa + Siguiente + Anterior + Barajar + Barajar Todo + Repetir + Repetir Todo + Repetir Uno + Abrir Cola + Buscar Música + + Ordenar Por + A-Z + Z-A + Artista + Álbum + Año + Número de Canción + Duración + Fecha Añadido + Lista de Pistas + Número de Caciones + Número de Álbumes + Nombre del Archivo + + Error al Reproducir la Pista + + Últimos Agregados + Reproducciones Recientes + Mis Mejores Pistas + + Reportar cualquier problema aquí + Especifique cualquier caracteristica nueva en la comnunidad de Google+ aquí + + Ver Como + Lista + Cuadrícula + + Claro + Oscuro + Negro + + Últimos Abiertos + + Canciones + Álbumes + Artistas + Libreria + Lista de Reproducción + Cola de Reproducción + Reproduciendo Ahora + Ajustes + Acerca de + Ayuda y Comentarios + Buscar + Barajar Todo + Ecualizador + Buscar en la Librería + + Seleccionar Tema + Elija el tema que se utilizará en toda la aplicación. + Elija la página de inicio que se mostrará en el lanzamiento. + Pagína de Inicio + Artistas en Cuadrícula + Toque para vizualizar los artistas en un cuadrícula de 2x2. + Tque para usar las animaciones del sistema en toda la aplicación. + Animaciones del Sistema + Toque para usar animaciones de transición en toda la aplicación. + Animaciones + Elija una pantalla de reproducción a partir de 4 estilos diferentes. + Personalización + Otro + Timber es un reproductor de música Open Source desarrollado por Naman Dwivedi. + Tocar para pausar la reproducción cuando se desconecta los auriculares + Pausar al Desconectar + Selección Actual + + Tema Oscuro + Aplicar el Tema Oscuro en toda la aplicación + + Color Primario + Cambiar el color primario del tema + + Color Énfasis + Cambiar el color de énfasis del tema + + Color de la Barra de Estado + Alternar coloración de la barra de estado + + Color de la Barra de Navegación + Alternar coloración de la barra de navegación + + %d canción + %d canciones + %d álbum + %d álbumes + %d artista + %d artistas + + %d canción agregada a la cola. + %d canciones agregada a la cola. + %d canción agregada a la lista de reproducción. + %d canciones agregada a la lista de reproducción. + + + + Timber + %1$s | %2$s + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + + From 386f51e0a09397d191652043629215f9f18c39eb Mon Sep 17 00:00:00 2001 From: naman14 Date: Wed, 6 Jan 2016 19:55:27 +0530 Subject: [PATCH 008/224] add wear controls --- .../main/java/com/naman14/timber/MusicService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java index 69b18941d..4ce089361 100644 --- a/app/src/main/java/com/naman14/timber/MusicService.java +++ b/app/src/main/java/com/naman14/timber/MusicService.java @@ -1054,7 +1054,10 @@ private void updateMediaSession(final String what) { if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mSession.setPlaybackState(new PlaybackState.Builder() - .setState(playState, position(), 1.0f).build()); + .setState(playState, position(), 1.0f) + .setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE | + PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS) + .build()); } } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) { Bitmap albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString()); @@ -1081,7 +1084,10 @@ private void updateMediaSession(final String what) { .build()); mSession.setPlaybackState(new PlaybackState.Builder() - .setState(playState, position(), 1.0f).build()); + .setState(playState, position(), 1.0f) + .setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE | + PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS) + .build()); } } } @@ -1095,9 +1101,6 @@ private Notification buildNotification() { int playButtonResId = isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp; - int playButtonTitleResId = isPlaying - ? R.string.accessibility_pause : R.string.accessibility_play; - Intent nowPlayingIntent = NavigationUtils.getNowPlayingIntent(this); PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT); From 0850e8fbb7b1c3ae8aca4f6a0ff47463fe3452a1 Mon Sep 17 00:00:00 2001 From: slmyldz41 Date: Mon, 11 Jan 2016 16:37:45 +0200 Subject: [PATCH 009/224] Implemented Turkish translate --- app/src/main/res/values-tr/strings.xml | 120 +++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 app/src/main/res/values-tr/strings.xml diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 000000000..499177d07 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,120 @@ + + + + Ayarlar + + Oynat + Durdur + İleri + Geri + Karıştır + Hepsini karıştır + Tekrarla + Hepsini tekrar et + Tekrarla + Sırayla oynat + Keşfet + + Sırala + A-Z + Z-A + Sanatçı + Albüm + Yıl + Parça numarası + Süre + Eklenme tarihi + Parça listesi + Müziklerin sayısı + Albümlerin sayısı + Dosya adı + + Parça oynatılırken hata oluştu + + Listeye eklendi + En son oynatılanlar + En çok dinlediklerim + + Hataları buraya bildirin. + Herhangi bir özellik eklenmesini istiyorsan Google+ gurubumuza buradan ulaşabilirsin + + Göster + Liste + Izgara + + Light + Dark + Black + + En son açılan + + Şarkılar + Albümler + Sanatçılar + Kütüphane + Oynatma listeleri + Oynatma sırası + Şimdi Oynatılıyor + Ayarlar + Hakkında + Yardım ve Geribildirim + Ayarlar + Hepsini karıştır + Ekolayzer + Kütüphanede Ara + + Tema seç + Bir tema seçin + Açılış sayfası seçin + Açılış sayfası + Sanatçıların Düzeni + Sanatçılar 2x2 ızgarada gösterilsin + Sistem animasyonları kullanılsın + Sistem animasyonları + Geçiş animasyonları kullanılsın + Animasyonlar + Şimdi oynatılıyor ekranını seçiniz + Kişiselleştirme + Diğer + Timber Naman Dwivedi tarafından geliştirilen açık kaynaklı Muzik oynatıcısıdır. + Toggle for pausing playback when headphone is detached + Duraklat(!!) + Seçilenler + + Dark Tema + Dark temayı kullan + + Birincil Renk + Birincil rengi değiştir + + Vurgu Rengi + Vurgu rengini değiştir + + Renkli Status Bar + Toggle status bar coloring + + Renkli Navigation Bar + Toggle navigation bar coloring + + %d şarkı + %d şarkılar + %d albüm + %d albümler + %d sanatçı + %d sanatçılar + + %d şarkı sıraya eklendi. + %d şarkılar sıraya eklendi. + %d şarkı oynatma listesine eklendi. + %d şarkılar oynatma listesine eklendi. + + + + Timber + %1$s | %2$s + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + + + From 2dcd9eb126de7e274449b0992f029943c5a267c2 Mon Sep 17 00:00:00 2001 From: slmyldz41 Date: Tue, 12 Jan 2016 09:45:54 +0200 Subject: [PATCH 010/224] Fixed Turkish translate --- app/src/main/res/values-tr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 499177d07..a71df3406 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -77,8 +77,8 @@ Kişiselleştirme Diğer Timber Naman Dwivedi tarafından geliştirilen açık kaynaklı Muzik oynatıcısıdır. - Toggle for pausing playback when headphone is detached - Duraklat(!!) + Kulaklığı çıkardığımda çalan şarkıyı durdur + Kulaklık Seçilenler Dark Tema From 7bacf85f297cb43d2019d36a1ba46ac6f90a93a5 Mon Sep 17 00:00:00 2001 From: Denis Maslennikov Date: Mon, 18 Jan 2016 09:59:41 +0300 Subject: [PATCH 011/224] Implemented Russian translate --- app/src/main/res/values-ru/strings.xml | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 app/src/main/res/values-ru/strings.xml diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 000000000..73d476e9e --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,119 @@ + + + + Настройки + + Играть + Пауза + Следующий + Предыдуший + Перемешать + Перемешать все + Повторить + Повторить все + Повторить один + Открыть очередь + Посмотреть музыку + + Сортировать по + A-Z + Z-A + Артисту + Альбому + Году + Номеру трека + Длительности + Дате добавления + Трек-листу + Количеству песен + Количеству альбомов + Имени файла + + Ошибка воспроизведения трека + + Новые + Недавно играли + Лучшие треки + + Сообщить о какой-либо ошибки здесь + Уточните любые вопросы в нашей Google+ группе здесь + + Показывать как + Список + Сетку + + Светлая + Темная + Черная + + Последний открытый + + Песни + Альбомы + Артисты + Библиотека + Плей-листы + Играет в очереди + Играет сейчас + Настройки + О приложении + Помощь и обратная связь + Поиск + Перемешать все + Эквалайзер + Искать в библиотеке + + Выбрать тему + Выбрать тему всего приложения. + Выбрать стартовую страницу, которая будет отображаться при запуске. + Стартовая страница + Артисты в сетке + Включить отображение артисов в сетке 2x2 + Включить системные анимации во всем приложении. + Системные анимации + Включить анимации в приложении. + Анимации + Выбрать тему для экрана проигрывания из 4-х различных стилей. + Персонализация + Прочее + Timber это музыкальный плеер с открытым исходным кодом который разработан Наманом Двиведи. + Включить автоматическую паузу при отсоединении наушников + Пауза при отсоединении + Сейчас выбрано + + Темная тема + Применить темную тему ко всему приложению + + Основной цвет + Изменить основной цвет темы + + Дополнительный цвет + Изменить дополнительный цвет темы + + Раскрасить статус бар + Включить раскраску статус бара + + Раскрасить панель навигации + Включить раскраску панели навигации + + %d песня + %d песни + %d альбом + %d альбомы + %d артист + %d артисты + + %d песня добавлена в очередь. + %d песни добавлены в очередь. + %d песня добавлена в плей-лист. + %d песни добавлены в плей-лист. + + + + Timber + %1$s | %2$s + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + + From 64111f7c07085ceefe39fe2b955aa6bcaff9da46 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 23 Jan 2016 15:35:06 +0100 Subject: [PATCH 012/224] add Track Selector lib --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 3 + .../java/com/naman14/timber/MusicService.java | 71 ++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 68895f1d2..8ccb336f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,7 @@ dependencies { compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0' compile 'com.squareup.okhttp:okhttp:2.3.0' compile 'com.google.code.gson:gson:2.3' + compile 'de.Maxr1998:track-selector-lib:1.1' compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { transitive = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0300c5e26..2db95bc66 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,10 @@ + + diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java index 4ce089361..361f93e79 100644 --- a/app/src/main/java/com/naman14/timber/MusicService.java +++ b/app/src/main/java/com/naman14/timber/MusicService.java @@ -43,6 +43,7 @@ import android.media.session.PlaybackState; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -78,6 +79,10 @@ import java.util.Random; import java.util.TreeSet; +import de.Maxr1998.trackselectorlib.ModNotInstalledException; +import de.Maxr1998.trackselectorlib.NotificationHelper; +import de.Maxr1998.trackselectorlib.TrackItem; + @SuppressLint("NewApi") public class MusicService extends Service { public static final String PLAYSTATE_CHANGED = "com.naman14.timber.playstatechanged"; @@ -142,6 +147,10 @@ public class MusicService extends Service { MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST, MediaStore.Audio.Albums.LAST_YEAR }; + private static final String[] NOTIFICATION_PROJECTION = new String[]{ + "audio._id AS _id", AudioColumns.ALBUM_ID, AudioColumns.TITLE, + AudioColumns.ARTIST, AudioColumns.DURATION + }; private static final Shuffler mShuffler = new Shuffler(); private static final int NOTIFY_MODE_NONE = 0; private static final int NOTIFY_MODE_FOREGROUND = 1; @@ -464,6 +473,11 @@ private void handleCommandIntent(Intent intent) { if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command); + if (NotificationHelper.checkIntent(intent)) { + goToPosition(mPlayPos + NotificationHelper.getPosition(intent)); + return; + } + if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) { gotoNext(true); } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action) @@ -1143,8 +1157,39 @@ private Notification buildNotification() { } if (artwork != null && TimberUtils.isLollipop()) builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor("#403f4d"))); + Notification n = builder.build(); - return builder.build(); + // Add Track Selector to notification + if (NotificationHelper.isSupported(n)) { + StringBuilder selection = new StringBuilder(); + StringBuilder order = new StringBuilder().append("CASE _id \n"); + for (int i = 0; i < mPlaylist.size(); i++) { + selection.append("_id=").append(mPlaylist.get(i).mId).append(" OR "); + order.append("WHEN ").append(mPlaylist.get(i).mId).append(" THEN ").append(i).append("\n"); + } + order.append("END"); + Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, NOTIFICATION_PROJECTION, selection.substring(0, selection.length() - 3), null, order.toString()); + if (c != null && c.getCount() != 0) { + c.moveToFirst(); + ArrayList list = new ArrayList<>(); + do { + TrackItem t = new TrackItem() + .setArt(ImageLoader.getInstance() + .loadImageSync(TimberUtils.getAlbumArtUri(c.getLong(c.getColumnIndexOrThrow(AudioColumns.ALBUM_ID))).toString())) + .setTitle(c.getString(c.getColumnIndexOrThrow(AudioColumns.TITLE))) + .setArtist(c.getString(c.getColumnIndexOrThrow(AudioColumns.ARTIST))) + .setDuration(TimberUtils.makeShortTimeString(this, c.getInt(c.getColumnIndexOrThrow(AudioColumns.DURATION)) / 1000)); + list.add(t.get()); + } while (c.moveToNext()); + try { + NotificationHelper.insertToNotification(n, list, this, getQueuePosition()); + } catch (ModNotInstalledException e) { + e.printStackTrace(); + } + c.close(); + } + } + return n; } private final PendingIntent retrievePlaybackAction(final String action) { @@ -1861,6 +1906,30 @@ public void gotoNext(final boolean force) { } } + public void goToPosition(int pos) { + synchronized (this) { + if (mPlaylist.size() <= 0) { + if (D) Log.d(TAG, "No play queue"); + scheduleDelayedShutdown(); + return; + } + if (pos < 0) { + return; + } + if (pos == mPlayPos) { + if (!isPlaying()) { + play(); + } + return; + } + stop(false); + setAndRecordPlayPos(pos); + openCurrentAndNext(); + play(); + notifyChange(META_CHANGED); + } + } + public void setAndRecordPlayPos(int nextPos) { synchronized (this) { From 9c2dd34a5be93d90c839668ae0fc5cf6185eee8f Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 23 Jan 2016 15:51:09 +0100 Subject: [PATCH 013/224] update build tools --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 69dea3377..51a0b9971 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-alpha3' + classpath 'com.android.tools.build:gradle:2.0.0-alpha7' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d1e3662cb..9851e5157 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Dec 31 18:07:01 IST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip From 0935af1fc5cd301f8ef8596ca626231a4d4e174b Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 23 Jan 2016 15:56:53 +0100 Subject: [PATCH 014/224] revert to older build tools, as new ones aren't on jCenter yet --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 51a0b9971..34d39a302 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-alpha7' + classpath 'com.android.tools.build:gradle:2.0.0-alpha6' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 25535145feae15441bb3b2c7868bf5929b422bac Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 18:34:56 +0530 Subject: [PATCH 015/224] make XTrackSelector optional --- .../main/java/com/naman14/timber/MusicService.java | 11 +++++++++-- .../com/naman14/timber/utils/PreferencesUtility.java | 5 +++++ app/src/main/res/values/strings.xml | 5 ++++- app/src/main/res/xml/preferences.xml | 10 ++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java index 361f93e79..6a974702c 100644 --- a/app/src/main/java/com/naman14/timber/MusicService.java +++ b/app/src/main/java/com/naman14/timber/MusicService.java @@ -67,6 +67,7 @@ import com.naman14.timber.provider.RecentStore; import com.naman14.timber.provider.SongPlayCount; import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.utils.TimberUtils.IdType; import com.nostra13.universalimageloader.core.ImageLoader; @@ -1159,7 +1160,14 @@ private Notification buildNotification() { builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor("#403f4d"))); Notification n = builder.build(); - // Add Track Selector to notification + if (PreferencesUtility.getInstance(this).getXPosedTrackselectorEnabled()) { + addXTrackSelector(n); + } + + return n; + } + + private void addXTrackSelector(Notification n) { if (NotificationHelper.isSupported(n)) { StringBuilder selection = new StringBuilder(); StringBuilder order = new StringBuilder().append("CASE _id \n"); @@ -1189,7 +1197,6 @@ private Notification buildNotification() { c.close(); } } - return n; } private final PendingIntent retrievePlaybackAction(final String action) { diff --git a/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java b/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java index d079c0fb2..961a0cd3f 100644 --- a/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java +++ b/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java @@ -38,6 +38,7 @@ public final class PreferencesUtility { private static final String START_PAGE_INDEX = "start_page_index"; private static final String START_PAGE_PREFERENCE_LASTOPENED = "start_page_preference_latopened"; private static final String NOW_PLAYNG_THEME_VALUE = "now_playing_theme_value"; + private static final String TOGGLE_XPOSED_TRACKSELECTOR = "toggle_xposed_trackselector"; private static PreferencesUtility sInstance; private static SharedPreferences mPreferences; @@ -207,4 +208,8 @@ public void setNowPlayingThemeChanged(final boolean value) { editor.putBoolean(NOW_PLAYNG_THEME_VALUE, value); editor.apply(); } + + public boolean getXPosedTrackselectorEnabled() { + return mPreferences.getBoolean(TOGGLE_XPOSED_TRACKSELECTOR, false); + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e056bb869..53756d7bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,10 +76,13 @@ Choose a now playing screen from 4 different styles. Personalisation Other + Advanced Timber is a open source Music Player developed by Naman Dwivedi. Toggle for pausing playback when headphone is detached Pause on Detach - Currently Selected + Toggle for pausing playback when headphone is detached + Enable playing queue support in notification + Requires Xposed and XMediaNotificationTrackSelector Xposed module to be installed. This will provide access to playing queue from the notification bar itself. Dark Theme Apply a dark theme throughout the app diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 669bdfe2c..8017423a4 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -102,4 +102,14 @@ + + + + + \ No newline at end of file From e51a0e7d5909436cc38a41c1c246a85a3eaf5c55 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 18:56:17 +0530 Subject: [PATCH 016/224] fix strings --- app/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 53756d7bd..b13644680 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,9 +80,9 @@ Timber is a open source Music Player developed by Naman Dwivedi. Toggle for pausing playback when headphone is detached Pause on Detach - Toggle for pausing playback when headphone is detached - Enable playing queue support in notification - Requires Xposed and XMediaNotificationTrackSelector Xposed module to be installed. This will provide access to playing queue from the notification bar itself. + Requires Xposed and XMediaNotificationTrackSelector Xposed module to be installed. This will provide access to playing queue from the notification bar itself. + Playing queue support in notification + Currently Selected Dark Theme Apply a dark theme throughout the app From d1498325bec081731441b97ee664c938eefb08d8 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 18:56:55 +0530 Subject: [PATCH 017/224] improve visualizerview --- .../java/com/naman14/timber/widgets/MusicVisualizer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java b/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java index 8128d0f0c..4ff455721 100644 --- a/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java +++ b/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java @@ -48,9 +48,9 @@ protected void onDraw(Canvas canvas) { //set paint style, Style.FILL will fill the color, Style.STROKE will stroke the color paint.setStyle(Paint.Style.FILL); - canvas.drawRect(getDimensionInPixel(0), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(7), getHeight(), paint); - canvas.drawRect(getDimensionInPixel(10), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(17), getHeight(), paint); - canvas.drawRect(getDimensionInPixel(20), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(27), getHeight(), paint); + canvas.drawRect(getDimensionInPixel(0), getHeight() - (20 + random.nextInt((int) (getHeight() / 1.5f) - 19)), getDimensionInPixel(7), getHeight(), paint); + canvas.drawRect(getDimensionInPixel(10), getHeight() - (20 + random.nextInt((int) (getHeight() / 1.5f) - 19)), getDimensionInPixel(17), getHeight(), paint); + canvas.drawRect(getDimensionInPixel(20), getHeight() - (20 + random.nextInt((int) (getHeight() / 1.5f) - 19)), getDimensionInPixel(27), getHeight(), paint); } public void setColor(int color) { From 09038731a87d482cdff4ea8335df69934235df31 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 19:17:21 +0530 Subject: [PATCH 018/224] use notificationcompat and MediaSessionCompat --- .../java/com/naman14/timber/MusicService.java | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java index 6a974702c..6835d26cb 100644 --- a/app/src/main/java/com/naman14/timber/MusicService.java +++ b/app/src/main/java/com/naman14/timber/MusicService.java @@ -19,7 +19,6 @@ import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; @@ -36,11 +35,8 @@ import android.graphics.Color; import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; -import android.media.MediaMetadata; import android.media.MediaPlayer; import android.media.audiofx.AudioEffect; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -56,6 +52,11 @@ import android.provider.MediaStore; import android.provider.MediaStore.Audio.AlbumColumns; import android.provider.MediaStore.Audio.AudioColumns; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.support.v7.app.NotificationCompat; import android.support.v7.graphics.Palette; import android.text.TextUtils; import android.util.Log; @@ -170,7 +171,7 @@ public class MusicService extends Service { private AlarmManager mAlarmManager; private PendingIntent mShutdownIntent; private boolean mShutdownScheduled; - private NotificationManager mNotificationManager; + private NotificationManagerCompat mNotificationManager; private Cursor mCursor; private Cursor mAlbumCursor; private AudioManager mAudioManager; @@ -183,7 +184,7 @@ public class MusicService extends Service { private boolean mQueueIsSaveable = true; private boolean mPausedByTransientLossOfFocus = false; - private MediaSession mSession; + private MediaSessionCompat mSession; private ComponentName mMediaButtonReceiverComponent; @@ -272,7 +273,7 @@ public void onCreate() { if (D) Log.d(TAG, "Creating service"); super.onCreate(); - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + mNotificationManager = NotificationManagerCompat.from(this); // gets a pointer to the playback state store mPlaybackStateStore = MusicPlaybackState.getInstance(this); @@ -344,8 +345,8 @@ public void onCreate() { } private void setUpMediaSession() { - mSession = new MediaSession(this, "Timber"); - mSession.setCallback(new MediaSession.Callback() { + mSession = new MediaSessionCompat(this, "Timber"); + mSession.setCallback(new MediaSessionCompat.Callback() { @Override public void onPause() { pause(); @@ -380,7 +381,7 @@ public void onStop() { releaseServiceUiAndStop(); } }); - mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); } @Override @@ -1063,15 +1064,15 @@ && getShuffleMode() != SHUFFLE_NONE) { private void updateMediaSession(final String what) { int playState = mIsSupposedToBePlaying - ? PlaybackState.STATE_PLAYING - : PlaybackState.STATE_PAUSED; + ? PlaybackStateCompat.STATE_PLAYING + : PlaybackStateCompat.STATE_PAUSED; if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mSession.setPlaybackState(new PlaybackState.Builder() + mSession.setPlaybackState(new PlaybackStateCompat.Builder() .setState(playState, position(), 1.0f) - .setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE | - PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS) + .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | + PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) .build()); } } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) { @@ -1085,23 +1086,23 @@ private void updateMediaSession(final String what) { albumArt = albumArt.copy(config, false); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mSession.setMetadata(new MediaMetadata.Builder() - .putString(MediaMetadata.METADATA_KEY_ARTIST, getArtistName()) - .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName()) - .putString(MediaMetadata.METADATA_KEY_ALBUM, getAlbumName()) - .putString(MediaMetadata.METADATA_KEY_TITLE, getTrackName()) - .putLong(MediaMetadata.METADATA_KEY_DURATION, duration()) - .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1) - .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, getQueue().length) - .putString(MediaMetadata.METADATA_KEY_GENRE, getGenreName()) - .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, + mSession.setMetadata(new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName()) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName()) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration()) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getQueue().length) + .putString(MediaMetadataCompat.METADATA_KEY_GENRE, getGenreName()) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, mShowAlbumArtOnLockscreen ? albumArt : null) .build()); - mSession.setPlaybackState(new PlaybackState.Builder() + mSession.setPlaybackState(new PlaybackStateCompat.Builder() .setState(playState, position(), 1.0f) - .setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE | - PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS) + .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | + PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) .build()); } } @@ -1130,7 +1131,7 @@ private Notification buildNotification() { mNotificationPostTime = System.currentTimeMillis(); } - Notification.Builder builder = new Notification.Builder(this) + android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notification) .setLargeIcon(artwork) .setContentIntent(clickIntent) @@ -1151,7 +1152,7 @@ private Notification buildNotification() { } if (TimberUtils.isLollipop()) { builder.setVisibility(Notification.VISIBILITY_PUBLIC); - Notification.MediaStyle style = new Notification.MediaStyle() + NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle() .setMediaSession(mSession.getSessionToken()) .setShowActionsInCompactView(0, 1, 2, 3); builder.setStyle(style); From eefbe440558075e432c3f4c0ab6f610b3d894fff Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 20:24:21 +0530 Subject: [PATCH 019/224] MediaBrowserService- Android wear and Auto --- app/src/main/AndroidManifest.xml | 8 + .../naman14/timber/WearBrowserService.java | 257 ++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 app/src/main/java/com/naman14/timber/WearBrowserService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2db95bc66..7d95919c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,14 @@ android:label="@string/app_name" android:process=":main" /> + + + + + + diff --git a/app/src/main/java/com/naman14/timber/WearBrowserService.java b/app/src/main/java/com/naman14/timber/WearBrowserService.java new file mode 100644 index 000000000..1fb610951 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/WearBrowserService.java @@ -0,0 +1,257 @@ +package com.naman14.timber; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.media.MediaDescription; +import android.media.browse.MediaBrowser; +import android.media.session.MediaSession; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.service.media.MediaBrowserService; +import android.support.annotation.Nullable; + +import com.naman14.timber.dataloaders.AlbumLoader; +import com.naman14.timber.dataloaders.ArtistLoader; +import com.naman14.timber.models.Album; +import com.naman14.timber.models.Artist; +import com.naman14.timber.utils.TimberUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by naman on 08/01/16. + */ +@TargetApi(21) +public class WearBrowserService extends MediaBrowserService { + + public static final String MEDIA_ID_ROOT = "__ROOT__"; + public static final int TYPE_ARTIST = 0; + public static final int TYPE_ALBUM = 1; + public static final int TYPE_SONG = 2; + public static final int TYPE_PLAYLIST = 3; + public static final int TYPE_ARTIST_SONGS = 0; + public static final int TYPE_ALBUM_SONGS = 1; + + MediaSession mSession; + public static WearBrowserService sInstance; + private List mQueryResult = new ArrayList(); + + private Context mContext; + private boolean mServiceStarted; + + public static WearBrowserService getInstance() { + return sInstance; + } + + @Override + public void onCreate() { + super.onCreate(); + sInstance = this; + mContext = this; + mSession = new MediaSession(this, "WearBrowserService"); + setSessionToken(mSession.getSessionToken()); + mSession.setCallback(new MediaSessionCallback()); + mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + + } + + @Override + public int onStartCommand(Intent startIntent, int flags, int startId) { + return START_STICKY; + } + + @Override + public void onDestroy() { + mServiceStarted = false; + mSession.release(); + } + + @Override + public void onLoadChildren(String parentId, Result> result) { + + result.detach(); + loadChildren(parentId, result); + + } + + @Nullable + @Override + public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { + return new BrowserRoot(MEDIA_ID_ROOT, null); + } + + private final class MediaSessionCallback extends MediaSession.Callback { + + @Override + public void onPlay() { + setSessionActive(); + } + + @Override + public void onSeekTo(long position) { + + } + + @Override + public void onPlayFromMediaId(final String mediaId, Bundle extras) { + setSessionActive(); + } + + @Override + public void onPause() { + + } + + @Override + public void onStop() { + setSessionInactive(); + } + + @Override + public void onSkipToNext() { + + } + + @Override + public void onSkipToPrevious() { + + } + + @Override + public void onFastForward() { + + } + + @Override + public void onRewind() { + + } + + @Override + public void onCustomAction(String action, Bundle extras) { + + } + } + + private void setSessionActive() { + if (!mServiceStarted) { + startService(new Intent(getApplicationContext(), WearBrowserService.class)); + mServiceStarted = true; + } + + if (!mSession.isActive()) { + mSession.setActive(true); + } + } + + private void setSessionInactive() { + if (mServiceStarted) { + stopSelf(); + mServiceStarted = false; + } + + if (mSession.isActive()) { + mSession.setActive(false); + } + } + + private void addMediaRoots(List mMediaRoot) { + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_ARTIST)) + .setTitle(getString(R.string.artists)) + .setIconUri(Uri.parse("android.resource://" + + "com.naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.artists)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_ALBUM)) + .setTitle(getString(R.string.albums)) + .setIconUri(Uri.parse("android.resource://" + + "com.naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.albums)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_SONG)) + .setTitle(getString(R.string.songs)) + .setIconUri(Uri.parse("android.resource://" + + "com.naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.songs)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_PLAYLIST)) + .setTitle(getString(R.string.playlists)) + .setIconUri(Uri.parse("android.resource://" + + "com.naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.playlists)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + } + + + private void loadChildren(final String parentId, final Result> result) { + + final List mediaItems = new ArrayList<>(); + + new AsyncTask() { + @Override + protected Void doInBackground(final Void... unused) { + + if (parentId.equals(MEDIA_ID_ROOT)) { + addMediaRoots(mediaItems); + } else { + switch (Integer.parseInt(parentId)) { + case TYPE_ARTIST: + + List artistList = ArtistLoader.getAllArtists(mContext); + for (Artist artist : artistList) { + String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount); + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount); + fillMediaItems(mediaItems, Integer.toString(11), artist.name, Uri.parse("android.resource://" + + "com.naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount)); + } + break; + case TYPE_ALBUM: + List albumList = AlbumLoader.getAllAlbums(mContext); + break; + + } + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + result.sendResult(mediaItems); + } + }.execute(); + + + } + + private void fillMediaItems(List mediaItems, String mediaId, String title, Uri icon, String subTitle) { + mediaItems.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(mediaId) + .setTitle(title) + .setIconUri(icon) + .setSubtitle(subTitle) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + } + +} From df340544f2bb91807cac7e4910d0512b16d7662e Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 21:11:43 +0530 Subject: [PATCH 020/224] WearBrowserService- browse album songs and play --- .../naman14/timber/WearBrowserService.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/WearBrowserService.java b/app/src/main/java/com/naman14/timber/WearBrowserService.java index 1fb610951..3b444b7da 100644 --- a/app/src/main/java/com/naman14/timber/WearBrowserService.java +++ b/app/src/main/java/com/naman14/timber/WearBrowserService.java @@ -13,9 +13,12 @@ import android.support.annotation.Nullable; import com.naman14.timber.dataloaders.AlbumLoader; +import com.naman14.timber.dataloaders.AlbumSongLoader; import com.naman14.timber.dataloaders.ArtistLoader; +import com.naman14.timber.dataloaders.SongLoader; import com.naman14.timber.models.Album; import com.naman14.timber.models.Artist; +import com.naman14.timber.models.Song; import com.naman14.timber.utils.TimberUtils; import java.util.ArrayList; @@ -32,12 +35,12 @@ public class WearBrowserService extends MediaBrowserService { public static final int TYPE_ALBUM = 1; public static final int TYPE_SONG = 2; public static final int TYPE_PLAYLIST = 3; - public static final int TYPE_ARTIST_SONGS = 0; - public static final int TYPE_ALBUM_SONGS = 1; + public static final int TYPE_ARTIST_SONG_ALBUMS = 4; + public static final int TYPE_ALBUM_SONGS = 5; + public static final int TYPE_ARTIST_ALL_SONGS = 6; MediaSession mSession; public static WearBrowserService sInstance; - private List mQueryResult = new ArrayList(); private Context mContext; private boolean mServiceStarted; @@ -97,7 +100,9 @@ public void onSeekTo(long position) { @Override public void onPlayFromMediaId(final String mediaId, Bundle extras) { + long songId = Long.parseLong(mediaId); setSessionActive(); + MusicPlayer.playAll(mContext, new long[]{songId}, 0, -1, TimberUtils.IdType.NA, false); } @Override @@ -164,7 +169,7 @@ private void addMediaRoots(List mMediaRoot) { .setMediaId(Integer.toString(TYPE_ARTIST)) .setTitle(getString(R.string.artists)) .setIconUri(Uri.parse("android.resource://" + - "com.naman14.timber/drawable/ic_empty_music2")) + "naman14.timber/drawable/ic_empty_music2")) .setSubtitle(getString(R.string.artists)) .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE )); @@ -174,7 +179,7 @@ private void addMediaRoots(List mMediaRoot) { .setMediaId(Integer.toString(TYPE_ALBUM)) .setTitle(getString(R.string.albums)) .setIconUri(Uri.parse("android.resource://" + - "com.naman14.timber/drawable/ic_empty_music2")) + "naman14.timber/drawable/ic_empty_music2")) .setSubtitle(getString(R.string.albums)) .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE )); @@ -184,7 +189,7 @@ private void addMediaRoots(List mMediaRoot) { .setMediaId(Integer.toString(TYPE_SONG)) .setTitle(getString(R.string.songs)) .setIconUri(Uri.parse("android.resource://" + - "com.naman14.timber/drawable/ic_empty_music2")) + "naman14.timber/drawable/ic_empty_music2")) .setSubtitle(getString(R.string.songs)) .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE )); @@ -195,7 +200,7 @@ private void addMediaRoots(List mMediaRoot) { .setMediaId(Integer.toString(TYPE_PLAYLIST)) .setTitle(getString(R.string.playlists)) .setIconUri(Uri.parse("android.resource://" + - "com.naman14.timber/drawable/ic_empty_music2")) + "naman14.timber/drawable/ic_empty_music2")) .setSubtitle(getString(R.string.playlists)) .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE )); @@ -214,19 +219,33 @@ protected Void doInBackground(final Void... unused) { if (parentId.equals(MEDIA_ID_ROOT)) { addMediaRoots(mediaItems); } else { - switch (Integer.parseInt(parentId)) { + switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) { case TYPE_ARTIST: - List artistList = ArtistLoader.getAllArtists(mContext); for (Artist artist : artistList) { String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount); String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount); - fillMediaItems(mediaItems, Integer.toString(11), artist.name, Uri.parse("android.resource://" + - "com.naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount)); + fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS), artist.name, Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE); } break; case TYPE_ALBUM: List albumList = AlbumLoader.getAllAlbums(mContext); + for (Album album : albumList) { + fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), album.artistName, MediaBrowser.MediaItem.FLAG_BROWSABLE); + } + break; + case TYPE_SONG: + List songList = SongLoader.getAllSongs(mContext); + for (Song song : songList) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; + case TYPE_ALBUM_SONGS: + List albumSongList = AlbumSongLoader.getSongsForAlbum(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : albumSongList) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } break; } @@ -240,17 +259,16 @@ protected void onPostExecute(Void aVoid) { } }.execute(); - } - private void fillMediaItems(List mediaItems, String mediaId, String title, Uri icon, String subTitle) { + private void fillMediaItems(List mediaItems, String mediaId, String title, Uri icon, String subTitle, int playableOrBrowsable) { mediaItems.add(new MediaBrowser.MediaItem( new MediaDescription.Builder() .setMediaId(mediaId) .setTitle(title) .setIconUri(icon) .setSubtitle(subTitle) - .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + .build(), playableOrBrowsable )); } From 37ede31450e65eef116e0dbfd4489c09390c6ff2 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 25 Jan 2016 21:36:47 +0530 Subject: [PATCH 021/224] finalise wearbrowserservice --- .../naman14/timber/WearBrowserService.java | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/WearBrowserService.java b/app/src/main/java/com/naman14/timber/WearBrowserService.java index 3b444b7da..db31e1523 100644 --- a/app/src/main/java/com/naman14/timber/WearBrowserService.java +++ b/app/src/main/java/com/naman14/timber/WearBrowserService.java @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2015 Naman Dwivedi + * + * Licensed under the GNU General Public License v3 + * + * This is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + */ + package com.naman14.timber; import android.annotation.TargetApi; @@ -14,19 +28,21 @@ import com.naman14.timber.dataloaders.AlbumLoader; import com.naman14.timber.dataloaders.AlbumSongLoader; +import com.naman14.timber.dataloaders.ArtistAlbumLoader; import com.naman14.timber.dataloaders.ArtistLoader; +import com.naman14.timber.dataloaders.ArtistSongLoader; +import com.naman14.timber.dataloaders.PlaylistLoader; +import com.naman14.timber.dataloaders.PlaylistSongLoader; import com.naman14.timber.dataloaders.SongLoader; import com.naman14.timber.models.Album; import com.naman14.timber.models.Artist; +import com.naman14.timber.models.Playlist; import com.naman14.timber.models.Song; import com.naman14.timber.utils.TimberUtils; import java.util.ArrayList; import java.util.List; -/** - * Created by naman on 08/01/16. - */ @TargetApi(21) public class WearBrowserService extends MediaBrowserService { @@ -38,6 +54,7 @@ public class WearBrowserService extends MediaBrowserService { public static final int TYPE_ARTIST_SONG_ALBUMS = 4; public static final int TYPE_ALBUM_SONGS = 5; public static final int TYPE_ARTIST_ALL_SONGS = 6; + public static final int TYPE_PLAYLIST_ALL_SONGS = 7; MediaSession mSession; public static WearBrowserService sInstance; @@ -225,7 +242,7 @@ protected Void doInBackground(final Void... unused) { for (Artist artist : artistList) { String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount); String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount); - fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS), artist.name, Uri.parse("android.resource://" + + fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS) + Long.toString(artist.id), artist.name, Uri.parse("android.resource://" + "naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE); } break; @@ -247,6 +264,37 @@ protected Void doInBackground(final Void... unused) { fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE); } break; + case TYPE_ARTIST_SONG_ALBUMS: + fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_ALL_SONGS) + Long.parseLong(parentId.substring(1)), "All songs", Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), "All songs by artist", MediaBrowser.MediaItem.FLAG_BROWSABLE); + List artistAlbums = ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1))); + for (Album album : artistAlbums) { + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, album.songCount); + fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE); + + } + break; + case TYPE_ARTIST_ALL_SONGS: + List artistSongs = ArtistSongLoader.getSongsForArtist(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : artistSongs) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; + case TYPE_PLAYLIST: + List playlistList = PlaylistLoader.getPlaylists(mContext, false); + for (Playlist playlist : playlistList) { + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, playlist.songCount); + fillMediaItems(mediaItems, Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id), playlist.name, + Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE); + } + break; + case TYPE_PLAYLIST_ALL_SONGS: + List playlistSongs = PlaylistSongLoader.getSongsInPlaylist(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : playlistSongs) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; } } From 1952a59e771286350a22b59beee8d87c45e4e049 Mon Sep 17 00:00:00 2001 From: naman14 Date: Thu, 28 Jan 2016 02:13:43 +0530 Subject: [PATCH 022/224] uodate to 015b --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8ccb336f2..498f432f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "naman14.timber" minSdkVersion 16 targetSdkVersion 23 - versionCode 8 - versionName "0.14b" + versionCode 9 + versionName "0.15b" //renderscript support mode is not supported for 21+ with gradle version 2.0 renderscriptTargetApi 20 renderscriptSupportModeEnabled true From 8a193e30e57b8919efcbbfeb500c514e56ec2078 Mon Sep 17 00:00:00 2001 From: Naman Dwivedi Date: Sun, 31 Jan 2016 00:09:20 +0530 Subject: [PATCH 023/224] fix google play badge link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d5f9cba7..53605457b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [WIP][BETA]-Material Design Music Player -[![Get it on Google Play](https://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=naman14.timber) +Get it on Google Play [![Get it on F-Droid](https://guardianproject.info/wp-content/uploads/2014/07/logo-fdroid.png)](https://f-droid.org/repository/browse/?fdid=naman14.timber) ## Screenshots From 241ae3acbeb1121ec233fee4e97a182b590e2457 Mon Sep 17 00:00:00 2001 From: naman14 Date: Wed, 3 Feb 2016 23:59:53 +0530 Subject: [PATCH 024/224] update build configuration --- app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 498f432f3..22407e873 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { } buildTypes { release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { @@ -27,6 +27,7 @@ android { } lintOptions { disable 'MissingTranslation' + disable 'ExtraTranslation' } } From 7cc78fa018ae5ee6e8bff36f8a9f8cf3577522b6 Mon Sep 17 00:00:00 2001 From: Markus B Date: Wed, 17 Feb 2016 18:09:46 +0100 Subject: [PATCH 025/224] add CATEGORY_APP_MUSIC to intent-filter --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7d95919c0..693fa98bd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + From 7436e432d8de5680c86ca8b9e36fa94188110817 Mon Sep 17 00:00:00 2001 From: Markus B Date: Wed, 17 Feb 2016 20:29:57 +0100 Subject: [PATCH 026/224] add ability to open musicfiles --- app/src/main/AndroidManifest.xml | 30 +++++++++++++++++++ .../java/com/naman14/timber/MusicPlayer.java | 9 ++++++ .../timber/activities/MainActivity.java | 13 ++++++++ 3 files changed, 52 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 693fa98bd..ed88db954 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Thu, 25 Feb 2016 09:06:10 +0100 Subject: [PATCH 027/224] Added Danish translation From 67857f0e71f6b27936bbe5473907c8b1bab0e345 Mon Sep 17 00:00:00 2001 From: Mikael Westermann Date: Thu, 25 Feb 2016 09:10:07 +0100 Subject: [PATCH 028/224] Added Danish translation I hope I know how to add this! --- app/src/main/res/values-da/strings.xml | 114 +++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 app/src/main/res/values-da/strings.xml diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml new file mode 100644 index 000000000..ee1c03289 --- /dev/null +++ b/app/src/main/res/values-da/strings.xml @@ -0,0 +1,114 @@ + + + + Indstillinger + + Afspil + Pause + Nste + Forrige + Bland + Bland alle + Gentag + Gentag alle + Gentag n + bn k + Gennemse musik + + Sortr + A-Z + Z-A + Kunstner + Album + r + Nummer + Varighed + Dato tilfjet + Spilleliste + Antal sange + Antal albums + Filnavn + + Fejl under afspilning + + Senest tilfjet + Afspillet for nylig + Mine Top Numre + + Rapportr eventuelle fejl her + Bed om nye features via Google+ fllesskabet her + + Vis som + Liste + Gitter + + Lyst + Mrkt + Sort + + Sidst bnet + + Sange + Albums + Kunstnere + Bibliotek + Afspilningslister + Afspilningsk + Afspilles Nu + Indstillinger + Om + Hjlp og Feedback + Sg + Bland Alle + Equalizer + Sg i Bibliotek + + Vlg Tema + Vlg et tema til appen. + Vlg en startside, der vises ved opstart. + Startside + Kunstnere i Gitter + Vlg for visning af kunstnere i et 2x2 gitter. + Vlg for systemanimationer i appen. + Systemanimationer + Vlg for Overgangsanimation i appen. + Animationer + Vlg en Afspilles Nu skrm blandt 4 udseender. + Personalisering + Andet + Avanceret + Timber er en open source Musikafspiller udviklet af Naman Dwivedi. + Vlg for at pause afspilning nr hovedtelefoner frakobles + Pause ved Frakobling + Krver Xposed og XMediaNotificationTrackSelector Xposed modul installeret. Dette giver adgang til afspilningsken fra selve notifikationslinjen. + Afspilningsk understttelse i notifikation + Valgt + + Mrkt Tema + Anvend et mrkt tema i appen + + Primr Farve + ndr den primre temafarve + + Accent Farve + ndr accent temafarven + + Farvet Statuslinje + Vlg statuslinje farvning + + Farvet Navigationslinje + Vlg navigationslinje farvning + + %d sang + %d sange + %d album + %d albums + %d kunstner + %d kunstnere + + %d sang tilfjet til ken. + %d sange tilfjet til ken. + %d sang tilfjet til afspilningslisten. + %d sange tilfjet til afspilningslisten. + + From f82e26ff920977112a9d3767b5422d39d83d9061 Mon Sep 17 00:00:00 2001 From: naman14 Date: Mon, 28 Mar 2016 13:20:30 +0530 Subject: [PATCH 029/224] fix build error --- app/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 22407e873..e3710f3af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,7 +56,7 @@ dependencies { compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { transitive = true } - compile('com.github.naman14:app-theme-engine:-SNAPSHOT@aar') { + compile('com.github.naman14:app-theme-engine:0.5.1@aar') { transitive = true } compile('com.github.afollestad.material-dialogs:commons:0.8.5.3@aar') { diff --git a/build.gradle b/build.gradle index 34d39a302..2ec58365d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-alpha6' + classpath 'com.android.tools.build:gradle:1.5.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From c0292da0d3050eebea123352e1dae80a5270e38a Mon Sep 17 00:00:00 2001 From: Naman Dwivedi Date: Sat, 4 Jun 2016 10:59:41 +0530 Subject: [PATCH 030/224] update to 23.3.0 --- app/build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e3710f3af..2ec91afed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion '23.0.2' + buildToolsVersion '23.0.3' defaultConfig { applicationId "naman14.timber" @@ -38,12 +38,12 @@ repositories { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.android.support:recyclerview-v7:23.1.1' - compile 'com.android.support:cardview-v7:23.1.1' - compile 'com.android.support:palette-v7:23.1.1' - compile 'com.android.support:design:23.1.1' - compile 'com.android.support:percent:23.1.1' + compile 'com.android.support:appcompat-v7:23.3.0' + compile 'com.android.support:recyclerview-v7:23.3.0' + compile 'com.android.support:cardview-v7:23.3.0' + compile 'com.android.support:palette-v7:23.3.0' + compile 'com.android.support:design:23.3.0' + compile 'com.android.support:percent:23.3.0' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4' compile 'net.steamcrafted:materialiconlib:1.0.3' From 1478b6a1e6ee97dd7f96f8dc726daf6c28d90084 Mon Sep 17 00:00:00 2001 From: nv95 Date: Fri, 8 Jul 2016 10:45:52 +0200 Subject: [PATCH 031/224] upgrade gradle version --- .../java/com/naman14/timber/activities/NowPlayingActivity.java | 1 - build.gradle | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java index 6cd258f0b..0afb04211 100644 --- a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java @@ -49,7 +49,6 @@ public int getLightToolbarMode() { return Config.LIGHT_TOOLBAR_AUTO; } - @Override public int getToolbarColor() { return Color.TRANSPARENT; } diff --git a/build.gradle b/build.gradle index 2ec58365d..c1a5b0a5d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.2.0-alpha4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 7fe2ba2b7073c947e35c3c99dc66220fea201d0c Mon Sep 17 00:00:00 2001 From: nv95 Date: Fri, 8 Jul 2016 15:53:20 +0200 Subject: [PATCH 032/224] Add desktop widget --- app/src/main/AndroidManifest.xml | 11 +++ .../widgets/desktop/StandardWidget.java | 99 +++++++++++++++++++ app/src/main/res/layout/widget_standard.xml | 94 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/widget_standard.xml | 6 ++ 5 files changed, 211 insertions(+) create mode 100644 app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java create mode 100644 app/src/main/res/layout/widget_standard.xml create mode 100644 app/src/main/res/xml/widget_standard.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed88db954..86049536b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -96,6 +96,17 @@ + + + + + + + + + + diff --git a/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java b/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java new file mode 100644 index 000000000..f9c524145 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java @@ -0,0 +1,99 @@ +package com.naman14.timber.widgets.desktop; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.text.TextUtils; +import android.widget.RemoteViews; + +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; +import com.naman14.timber.R; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.TimberUtils; +import com.nostra13.universalimageloader.core.ImageLoader; + +/** + * Created by nv95 on 08.07.16. + */ + +public class StandardWidget extends AppWidgetProvider { + + private static final int REQUEST_NEXT = 1; + private static final int REQUEST_PREV = 2; + private static final int REQUEST_PLAYPAUSE = 3; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + ComponentName serviceName = new ComponentName(context, MusicService.class); + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_standard); + remoteViews.setOnClickPendingIntent(R.id.image_next, PendingIntent.getService( + context, + REQUEST_NEXT, + new Intent(context, MusicService.class) + .setAction(MusicService.NEXT_ACTION) + .setComponent(serviceName), + 0 + )); + remoteViews.setOnClickPendingIntent(R.id.image_prev, PendingIntent.getService( + context, + REQUEST_PREV, + new Intent(context, MusicService.class) + .setAction(MusicService.PREVIOUS_ACTION) + .setComponent(serviceName), + 0 + )); + remoteViews.setOnClickPendingIntent(R.id.image_playpause, PendingIntent.getService( + context, + REQUEST_PLAYPAUSE, + new Intent(context, MusicService.class) + .setAction(MusicService.TOGGLEPAUSE_ACTION) + .setComponent(serviceName), + 0 + )); + String t = MusicPlayer.getTrackName(); + if (t != null) { + remoteViews.setTextViewText(R.id.textView_title, t); + } + t = MusicPlayer.getArtistName(); + if (t != null) { + String album = MusicPlayer.getAlbumName(); + if (!TextUtils.isEmpty(album)) { + t += " - " + album; + } + remoteViews.setTextViewText(R.id.textView_subtitle, t); + } + remoteViews.setImageViewResource(R.id.image_playpause, + MusicPlayer.isPlaying() ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp); + Bitmap artwork; + artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(MusicPlayer.getCurrentAlbumId()).toString()); + if (artwork == null) { + artwork = ImageLoader.getInstance().loadImageSync("drawable://" + R.drawable.ic_empty_music2); + } + remoteViews.setImageViewBitmap(R.id.imageView_cover, artwork); + remoteViews.setOnClickPendingIntent(R.id.imageView_cover, PendingIntent.getActivity( + context, + 0, + NavigationUtils.getNowPlayingIntent(context), + PendingIntent.FLAG_UPDATE_CURRENT + )); + appWidgetManager.updateAppWidget(appWidgetIds, remoteViews); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null && action.startsWith("com.naman14.timber.")) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + ComponentName thisAppWidget = new ComponentName(context.getPackageName(), this.getClass().getName()); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget); + onUpdate(context, appWidgetManager, appWidgetIds); + } else { + super.onReceive(context, intent); + } + } +} diff --git a/app/src/main/res/layout/widget_standard.xml b/app/src/main/res/layout/widget_standard.xml new file mode 100644 index 000000000..6679ec9d6 --- /dev/null +++ b/app/src/main/res/layout/widget_standard.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b13644680..038ed4800 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,5 +118,6 @@ %1$s %2$s %2$d:%3$02d %1$d:%2$02d:%3$02d + Timber standard widget diff --git a/app/src/main/res/xml/widget_standard.xml b/app/src/main/res/xml/widget_standard.xml new file mode 100644 index 000000000..bdab56a43 --- /dev/null +++ b/app/src/main/res/xml/widget_standard.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file From e5de3cec568ec212e6fcc3d2ea08d8fe8eb7d252 Mon Sep 17 00:00:00 2001 From: nv95 Date: Fri, 8 Jul 2016 21:31:20 +0200 Subject: [PATCH 033/224] Fix toolbar background on NowPlayingActivity --- .../timber/activities/NowPlayingActivity.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java index 0afb04211..b81515e05 100644 --- a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java @@ -3,11 +3,14 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; +import android.support.v7.app.ActionBar; import com.afollestad.appthemeengine.Config; import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer; @@ -45,12 +48,17 @@ public int getActivityTheme() { } @Override - public int getLightToolbarMode() { - return Config.LIGHT_TOOLBAR_AUTO; + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + } } - public int getToolbarColor() { - return Color.TRANSPARENT; + @Override + public int getLightToolbarMode() { + return Config.LIGHT_TOOLBAR_AUTO; } @Override From 77c242ef8b2631979a3973af3d7b1dfae185ae7c Mon Sep 17 00:00:00 2001 From: nv95 Date: Sat, 9 Jul 2016 11:29:02 +0200 Subject: [PATCH 034/224] Set default volume control stream --- .../java/com/naman14/timber/activities/BaseActivity.java | 6 ++++-- .../naman14/timber/activities/BaseThemedActivity.java | 9 +++++++++ build.gradle | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/activities/BaseActivity.java b/app/src/main/java/com/naman14/timber/activities/BaseActivity.java index 0953cc3ad..0d31b6387 100644 --- a/app/src/main/java/com/naman14/timber/activities/BaseActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/BaseActivity.java @@ -21,6 +21,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.media.AudioManager; +import android.media.session.MediaSessionManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -63,7 +65,8 @@ public void onCreate(Bundle savedInstanceState) { mToken = MusicPlayer.bindToService(this, this); mPlaybackStatus = new PlaybackStatus(this); - + //make volume keys change multimedia volume even if music is not playing now + setVolumeControlStream(AudioManager.STREAM_MUSIC); } @Override @@ -126,7 +129,6 @@ protected void onDestroy() { } catch (final Throwable e) { } mMusicStateListener.clear(); - } @Override diff --git a/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java b/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java index 7ab499149..f46d09c9f 100644 --- a/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java @@ -1,5 +1,7 @@ package com.naman14.timber.activities; +import android.media.AudioManager; +import android.os.Bundle; import android.support.annotation.Nullable; import com.afollestad.appthemeengine.ATEActivity; @@ -15,4 +17,11 @@ public class BaseThemedActivity extends ATEActivity { public String getATEKey() { return Helpers.getATEKey(this); } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //make volume keys change multimedia volume even if music is not playing now + setVolumeControlStream(AudioManager.STREAM_MUSIC); + } } diff --git a/build.gradle b/build.gradle index c1a5b0a5d..4b4a17cd2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0-alpha4' + classpath 'com.android.tools.build:gradle:2.2.0-alpha5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From f3c4d8d73d1d812d231eba615063f2a95947b50c Mon Sep 17 00:00:00 2001 From: nv95 Date: Sat, 9 Jul 2016 12:01:25 +0200 Subject: [PATCH 035/224] Fix NowPlaying toolbar color --- .../timber/activities/NowPlayingActivity.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java index b81515e05..dc235b9ae 100644 --- a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java @@ -6,7 +6,6 @@ import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -44,16 +43,6 @@ public void onCreate(Bundle savedInstanceState) { @Override public int getActivityTheme() { return PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light; - - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - } } @Override @@ -68,5 +57,9 @@ public void onResume() { PreferencesUtility.getInstance(this).setNowPlayingThemeChanged(false); recreate(); } + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + } } } From 1d88b8bd5792a453e7b689a7087ecdd813db64c7 Mon Sep 17 00:00:00 2001 From: nv95 Date: Sat, 9 Jul 2016 12:08:36 +0200 Subject: [PATCH 036/224] Extract strings --- app/src/main/res/layout/layout_about_dialog.xml | 2 +- app/src/main/res/menu/menu_playlist.xml | 2 +- app/src/main/res/menu/popup_playing_queue.xml | 8 ++++---- app/src/main/res/menu/popup_song.xml | 12 ++++++------ app/src/main/res/values/strings.xml | 7 +++++++ 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/layout_about_dialog.xml b/app/src/main/res/layout/layout_about_dialog.xml index 0657b7ada..6b9b8b06e 100644 --- a/app/src/main/res/layout/layout_about_dialog.xml +++ b/app/src/main/res/layout/layout_about_dialog.xml @@ -95,7 +95,7 @@ android:id="@+id/dismiss_dialog" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="OK" + android:text="@android:string/ok" style="@style/TextAppearance.AppCompat.Body1" android:textSize="18sp" android:layout_gravity="right" diff --git a/app/src/main/res/menu/menu_playlist.xml b/app/src/main/res/menu/menu_playlist.xml index ff8c39b1c..a62206f97 100644 --- a/app/src/main/res/menu/menu_playlist.xml +++ b/app/src/main/res/menu/menu_playlist.xml @@ -5,7 +5,7 @@ \ No newline at end of file diff --git a/app/src/main/res/menu/popup_playing_queue.xml b/app/src/main/res/menu/popup_playing_queue.xml index 598b29cca..fce2206b3 100644 --- a/app/src/main/res/menu/popup_playing_queue.xml +++ b/app/src/main/res/menu/popup_playing_queue.xml @@ -3,17 +3,17 @@ + android:title="@string/play" /> + android:title="@string/add_to_playlist" /> + android:title="@string/go_to_album" /> + android:title="@string/go_to_artist" /> \ No newline at end of file diff --git a/app/src/main/res/menu/popup_song.xml b/app/src/main/res/menu/popup_song.xml index b4da3e2c5..04ee99776 100644 --- a/app/src/main/res/menu/popup_song.xml +++ b/app/src/main/res/menu/popup_song.xml @@ -3,25 +3,25 @@ + android:title="@string/play" /> + android:title="@string/play_next" /> + android:title="@string/add_to_queue" /> + android:title="@string/add_to_playlist" /> + android:title="@string/go_to_album" /> + android:title="@string/go_to_artist" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 038ed4800..f112b6136 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,5 +119,12 @@ %2$d:%3$02d %1$d:%2$02d:%3$02d Timber standard widget + Play + Add to playlist + Go to album + Go to artist + Add to queue + Play next + New playlist From 591fc1f26800d2dde5d3af5b92b2c571d5b2c7d6 Mon Sep 17 00:00:00 2001 From: nv95 Date: Sat, 9 Jul 2016 12:12:20 +0200 Subject: [PATCH 037/224] Update russian translation --- app/src/main/res/values-ru/strings.xml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 73d476e9e..72f039576 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -108,12 +108,13 @@ %d песня добавлена в плей-лист. %d песни добавлены в плей-лист. - - - Timber - %1$s | %2$s - %1$s %2$s - %2$d:%3$02d - %1$d:%2$02d:%3$02d + Добавить в список + В очередь + Дополнительно + Альбом + Исполнитель + Новый плейлист + Воспроизвести + Воспр. следующим From f3747dd5b66651b75c51142f07ac750c3d2a98fc Mon Sep 17 00:00:00 2001 From: olgierd-on-fire Date: Tue, 12 Jul 2016 15:39:23 +0900 Subject: [PATCH 038/224] Add Korean translation --- app/src/main/res/values-ko/strings.xml | 114 +++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 app/src/main/res/values-ko/strings.xml diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 000000000..8996633e6 --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,114 @@ + + + + 설정 + + 재생 + 일시 중지 + 다음 + 이전 + 섞기 + 전체 섞기 + 반복 + 전체 반복 + 한 번 반복 + 재생 대기열 열기 + 음악 찾기 + + 정렬 + A-Z + Z-A + 아티스트 + 앨범 + 연도 + 트랙 번호 + 길이 + 추가된 날짜 + 트랙 목록 + 음악 수 + 앨범 수 + 파일명 + + 재생 중인 트랙 오류 + + 마지막으로 추가된 리스트 + 최근 재생한 리스트 + 상위 트랙 + + 여기에 버그를 남겨주세요 + Google+ 커뮤니티에 새로운 기능을 요청하세요 + + 보기 정렬 + 목록 + 그리드 + + 밝게 + 어둡게 + 까맣게 + + 마지막으로 본 화면 + + 음악 + 앨범 + 아티스트 + 저장소 + 재생 목록 + 재생 중인 대기열 + 재생 주 + 설정 + 소개 + 도움말과 피드백 + 찾기 + 전체 섞기 + 이퀄라이저 + 저장소 찾기 + + 테마 선택 + 전체적인 테마를 고르세요. + 어플 실행 시 보여질 화면을 선택하세요. + 시작 화면 + 그리드로 아티스트 보기 + 2x2 그리드로 아티스트 보기를 유지합니다. + 시스템 애니메이션을 유지합니다. + 시스템 애니메이션 + 변환 애니메이션을 유지합니다. + 애니메이션 + 4가지 다른 스타일 중 재생 중인 화면을 고르세요. + 개인 설정 + 기타 + 고급 + 팀버(Timber)는 Naman Dwivedi가 개발한 오픈 소스 음악 플레이어입니다. + 이어폰이 분리되면 일시 중지합니다. + 분리 시 일시 중지 + Xposed와 XMediaNotificationTrackSelector Xposed 모듈이 설치되어 있어야 합니다. 이들은 알림바에서 재생 중인 대기열에 접근하기 위해 사용됩니다. + 재생 중인 대기열 알림 활성화 + 현재 선택된 것 + + 어두운 테마 + 어두운 테마를 적용합니다 + + 기본 색깔 + 기본 테마 색깔을 변경합니다 + + 강조 색깔 + 강조 테마 색깔을 변경합니다 + + 색깔 있는 상태바 + 상태바 색깔을 유지합니다 + + 색깔 있는 내비게이션바 + 내비게이션바 색깔을 유지합니다 + + %d 음악 + %d 음악 + %d 앨범 + %d 앨범 + %d 음악가 + %d 음악가 + + %d 음악이 대기열에 추가됐습니다. + %d 음악이 대기열에 추가됐습니다. + %d 음악이 재생 목록에 추가됐습니다. + %d 음악이 재생 목록에 추가됐습니다. + + From 8de6faa337df034581f6da43046ef1d73e78d9e5 Mon Sep 17 00:00:00 2001 From: naman14 Date: Tue, 12 Jul 2016 13:04:13 +0530 Subject: [PATCH 039/224] add indonesian translations --- app/src/main/res/values-id/strings.xml | 111 +++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 app/src/main/res/values-id/strings.xml diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 000000000..dee83489a --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,111 @@ + + + + Timber + Pengaturan + + Main + Jeda + Berikutnya + Sebelumnya + Acak + Acak semua + Ulangi + Ulangi semua + Ulangi satu kali + Buka antrian + Telusuri musik + + Urutkan berdasarkan + A-Z + Z-A + Artis + Album + Tahun + Nomor Lagu + Durasi + Tanggal Ditambahkan + Daftar Lagu + Jumlah Lagu + Jumlah Album + Nama File + + Kesalahan Saat Memutar lagu + + Terakhir Ditambahkan + Baru-baru ini Dimainkan + Lagu Teratas Saya + + %1$s | %2$s + + %1$s %2$s + %2$d:%3$02d + %1$d:%2$02d:%3$02d + Laporkan bug di sini + Spesifikasikan permintaan fitur dalam komunitas Google+ di sini + + Lihat Sebagai + Daftar + Grid + + Terang + Gelap + Hitam + + Terakhir Dibuka + + Lagu + Album + Artis + Pustaka + Daftar Putar + Antrian Main + Sedang Dimainkan + Pengaturan + Tentang + Bantuan dan Umpan Balik + Cari + Acak semua + Ekualiser + Cari Pustaka + %d song + %d songs + %d album + %d albums + %d artist + %d artists + Pilih Tema + Pilih tema yang akan digunakan di seluruh aplikasi. + Pilih halaman awal yang akan ditampilkan pada saat peluncuran. + Halaman awal + Artis dalam Grid + Toggle untuk menampilkan artis dalam grid 2x2. + Toggle untuk untuk animasi sistem di seluruh aplikasi. + Animasi Sistem + Toggle untuk animasi transisi di seluruh aplikasi.on throughout the app. + Animasi + Pilih layar yang sedang diputar dari 4 gaya yang berbeda. + Personalisasi + Lainnya + Timber adalah Pemutar Musik dengan Sumber Terbuka yang dikembangkan oleh Naman Dwivedi. + Berhenti memutar ketika headphone dilepas + Jeda saat dilepas + Yang Sedang Dipilih + + + Tema Gelap + Terapkan tema gelap di seluruh aplikasi + + Warna Utama + Mengubah warna tema utama + + Warna Aksen + Mengubah warna aksen tema + + Bilah Status Berwarna + Mewarnai bilah status. + + Bilah Navigasi Berwarna + Mewarnai bilah navigasi + + From cded54b2623cdfb222fc192db271f8f52ae47ec9 Mon Sep 17 00:00:00 2001 From: Christoph Walcher Date: Sun, 17 Jul 2016 21:55:39 +0200 Subject: [PATCH 040/224] LastFM --- .../java/com/naman14/timber/MusicService.java | 9 ++ .../timber/activities/NowPlayingActivity.java | 2 +- .../timber/dialogs/LastFmLoginDialog.java | 54 ++++++++++++ .../timber/fragments/SettingsFragment.java | 34 ++++++++ .../timber/lastfmapi/LastFmClient.java | 86 ++++++++++++++++++- .../lastfmapi/LastFmUserRestService.java | 26 ++++++ .../timber/lastfmapi/RestServiceFactory.java | 19 +++- .../lastfmapi/callbacks/UserListener.java | 13 +++ .../lastfmapi/models/LastfmUserSession.java | 43 ++++++++++ .../timber/lastfmapi/models/ScrobbleInfo.java | 7 ++ .../lastfmapi/models/ScrobbleQuery.java | 34 ++++++++ .../lastfmapi/models/UserLoginInfo.java | 16 ++++ .../lastfmapi/models/UserLoginQuery.java | 29 +++++++ .../main/res/layout/dialog_lastfm_login.xml | 21 +++++ app/src/main/res/values-id/strings.xml | 4 +- app/src/main/res/xml/preferences.xml | 10 +++ 16 files changed, 402 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java create mode 100644 app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java create mode 100644 app/src/main/res/layout/dialog_lastfm_login.xml diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java index 6835d26cb..9b2069300 100644 --- a/app/src/main/java/com/naman14/timber/MusicService.java +++ b/app/src/main/java/com/naman14/timber/MusicService.java @@ -63,6 +63,8 @@ import com.naman14.timber.helpers.MediaButtonIntentReceiver; import com.naman14.timber.helpers.MusicPlaybackTrack; +import com.naman14.timber.lastfmapi.LastFmClient; +import com.naman14.timber.lastfmapi.models.ScrobbleQuery; import com.naman14.timber.permissions.Nammu; import com.naman14.timber.provider.MusicPlaybackState; import com.naman14.timber.provider.RecentStore; @@ -450,6 +452,11 @@ public int onStartCommand(final Intent intent, final int flags, final int startI return START_STICKY; } + void scrobble() { + Log.d("Scrobble","to LastFM"); + LastFmClient.getInstance(this).Scrobble(new ScrobbleQuery(getArtistName(),getTrackName(),(System.currentTimeMillis()-duration())/1000)); + } + private void releaseServiceUiAndStop() { if (isPlaying() || mPausedByTransientLossOfFocus @@ -2150,6 +2157,7 @@ public void handleMessage(final Message msg) { } break; case TRACK_WENT_TO_NEXT: + mService.get().scrobble(); service.setAndRecordPlayPos(service.mNextPlayPos); service.setNextTrack(); if (service.mCursor != null) { @@ -2161,6 +2169,7 @@ public void handleMessage(final Message msg) { service.updateNotification(); break; case TRACK_ENDED: + mService.get().scrobble(); if (service.mRepeatMode == REPEAT_CURRENT) { service.seek(0); service.play(); diff --git a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java index 6cd258f0b..b0f664ec5 100644 --- a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java @@ -49,7 +49,7 @@ public int getLightToolbarMode() { return Config.LIGHT_TOOLBAR_AUTO; } - @Override + //@Override public int getToolbarColor() { return Color.TRANSPARENT; } diff --git a/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java b/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java new file mode 100644 index 000000000..d8c3491f4 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java @@ -0,0 +1,54 @@ +package com.naman14.timber.dialogs; + +import android.annotation.TargetApi; +import android.app.Dialog; +import android.app.DialogFragment; +import android.os.Build; +import android.os.Bundle; +import android.preference.Preference; +import android.support.annotation.NonNull; +import android.util.Log; +import android.widget.EditText; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.R; +import com.naman14.timber.fragments.PlaylistFragment; +import com.naman14.timber.fragments.SettingsFragment; +import com.naman14.timber.lastfmapi.LastFmClient; +import com.naman14.timber.lastfmapi.callbacks.UserListener; +import com.naman14.timber.lastfmapi.models.UserLoginQuery; + +/** + * Created by christoph on 17.07.16. + */ +public class LastFmLoginDialog extends DialogFragment { + public static final String FRAGMENT_NAME = "LastFMLogin"; + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new MaterialDialog.Builder(getActivity()).positiveText("Login").negativeText("Cancel").customView(R.layout.dialog_lastfm_login,false).onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + String username = ((EditText)dialog.findViewById(R.id.lastfm_username)).getText().toString(); + String password = ((EditText)dialog.findViewById(R.id.lastfm_password)).getText().toString(); + if(username.length()==0||password.length()==0)return; + LastFmClient.getInstance(getActivity()).getUserLoginInfo(new UserLoginQuery(username, password), new UserListener() { + + @Override + public void userSuccess() { + if (getTargetFragment() instanceof SettingsFragment) { + ((SettingsFragment) getTargetFragment()).updateLastFM(); + } + } + + @Override + public void userInfoFailed() { + Toast.makeText(getTargetFragment().getActivity(),"Failed to Login",Toast.LENGTH_SHORT).show(); + } + }); + } + }).build(); + } +} diff --git a/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java b/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java index 323e6ac4b..7373d4f06 100644 --- a/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java @@ -30,6 +30,8 @@ import com.afollestad.materialdialogs.color.ColorChooserDialog; import com.naman14.timber.R; import com.naman14.timber.activities.SettingsActivity; +import com.naman14.timber.dialogs.LastFmLoginDialog; +import com.naman14.timber.lastfmapi.LastFmClient; import com.naman14.timber.utils.Constants; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; @@ -37,13 +39,16 @@ public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String NOW_PLAYING_SELECTOR = "now_playing_selector"; + private static final String LASTFM_LOGIN = "lastfm_login"; private static final String KEY_ABOUT = "preference_about"; private static final String KEY_SOURCE = "preference_source"; private static final String KEY_THEME = "theme_preference"; private static final String TOGGLE_ANIMATIONS = "toggle_animations"; private static final String TOGGLE_SYSTEM_ANIMATIONS = "toggle_system_animations"; private static final String KEY_START_PAGE = "start_page_preference"; + private boolean lastFMlogedin; Preference nowPlayingSelector; + Preference lastFMlogin; SwitchPreference toggleAnimations; ListPreference themePreference, startPagePreference; PreferencesUtility mPreferences; @@ -58,10 +63,27 @@ public void onCreate(Bundle savedInstanceState) { mPreferences = PreferencesUtility.getInstance(getActivity()); nowPlayingSelector = findPreference(NOW_PLAYING_SELECTOR); + lastFMlogin = findPreference(LASTFM_LOGIN); + updateLastFM(); // themePreference = (ListPreference) findPreference(KEY_THEME); startPagePreference = (ListPreference) findPreference(KEY_START_PAGE); nowPlayingSelector.setIntent(NavigationUtils.getNavigateToStyleSelectorIntent(getActivity(), Constants.SETTINGS_STYLE_SELECTOR_NOWPLAYING)); + lastFMlogin.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (lastFMlogedin) { + LastFmClient.getInstance(getActivity()).logout(); + updateLastFM(); + } else { + LastFmLoginDialog lastFmLoginDialog = new LastFmLoginDialog(); + lastFmLoginDialog.setTargetFragment(SettingsFragment.this, 0); + lastFmLoginDialog.show(getFragmentManager(), LastFmLoginDialog.FRAGMENT_NAME); + + } + return true; + } + }); PreferencesUtility.getInstance(getActivity()).setOnSharedPreferenceChangeListener(this); setPreferenceClickListeners(); @@ -187,4 +209,16 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } + public void updateLastFM() { + String username = LastFmClient.getInstance(getActivity()).getUsername(); + if (username != null) { + lastFMlogedin = true; + lastFMlogin.setTitle("Logout"); + lastFMlogin.setSummary("Logged in as " + username); + } else { + lastFMlogedin = false; + lastFMlogin.setTitle("Login"); + lastFMlogin.setSummary("Login to LastFM to scrobble"); + } + } } diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java index 534aa94bc..3d30c8ef8 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java @@ -18,10 +18,21 @@ import android.util.Log; import com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener; +import com.naman14.timber.lastfmapi.callbacks.UserListener; import com.naman14.timber.lastfmapi.models.AlbumInfo; import com.naman14.timber.lastfmapi.models.AlbumQuery; import com.naman14.timber.lastfmapi.models.ArtistInfo; import com.naman14.timber.lastfmapi.models.ArtistQuery; +import com.naman14.timber.lastfmapi.models.LastfmUserSession; +import com.naman14.timber.lastfmapi.models.ScrobbleInfo; +import com.naman14.timber.lastfmapi.models.ScrobbleQuery; +import com.naman14.timber.lastfmapi.models.UserLoginInfo; +import com.naman14.timber.lastfmapi.models.UserLoginQuery; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import retrofit.Callback; import retrofit.RetrofitError; @@ -29,23 +40,55 @@ public class LastFmClient { + public static final String API_KEY = "62ac1851456e4558bef1c41747b1aec2"; + public static final String API_SECRET = "b4ae8965723d67fb18e35d207014d6f3"; + + public static final String JSON = "json"; + public static final String BASE_API_URL = "http://ws.audioscrobbler.com/2.0"; + public static final String BASE_SECURE_API_URL = "https://ws.audioscrobbler.com/2.0"; private static LastFmClient sInstance; private LastFmRestService mRestService; + private LastFmUserRestService mUserRestService; + private Context context; + + private LastfmUserSession mUserSession; private static final Object sLock = new Object(); public static LastFmClient getInstance(Context context) { synchronized (sLock) { if (sInstance == null) { sInstance = new LastFmClient(); - sInstance.mRestService = RestServiceFactory.create(context, BASE_API_URL, LastFmRestService.class); + sInstance.context = context; + sInstance.mRestService = RestServiceFactory.createStatic(context, BASE_API_URL, LastFmRestService.class); + sInstance.mUserRestService = RestServiceFactory.create(context, BASE_SECURE_API_URL, LastFmUserRestService.class); + sInstance.mUserSession = LastfmUserSession.getSession(context); + } return sInstance; } } + static String generateMD5(String in) { + byte[] bytesOfMessage = new byte[0]; + try { + bytesOfMessage = in.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(bytesOfMessage); + String out = ""; + for (byte symbol : digest) { + out += String.format("%02X", symbol); + } + return out; + } catch (UnsupportedEncodingException | NoSuchAlgorithmException ignored) { + return null; + } + + + } + public void getAlbumInfo(AlbumQuery albumQuery) { mRestService.getAlbumInfo(albumQuery.mArtist, albumQuery.mALbum, new Callback() { @Override @@ -76,4 +119,45 @@ public void failure(RetrofitError error) { } }); } + + public void getUserLoginInfo(UserLoginQuery userLoginQuery, final UserListener listener) { + mUserRestService.getUserLoginInfo(UserLoginQuery.Method, JSON, API_KEY, generateMD5(userLoginQuery.getSignature()), userLoginQuery.mUsername, userLoginQuery.mPassword, new Callback() { + @Override + public void success(UserLoginInfo userLoginInfo, Response response) { + Log.d("Logedin", userLoginInfo.mSession.mToken + " " + userLoginInfo.mSession.mUsername); + mUserSession = userLoginInfo.mSession; + mUserSession.update(context); + listener.userSuccess(); + } + + @Override + public void failure(RetrofitError error) { + listener.userInfoFailed(); + } + }); + } + + public void Scrobble(ScrobbleQuery scrobbleQuery) { + mUserRestService.getScrobbleInfo(ScrobbleQuery.Method, API_KEY, generateMD5(scrobbleQuery.getSignature(mUserSession.mToken)), mUserSession.mToken, scrobbleQuery.mArtist, scrobbleQuery.mTrack, scrobbleQuery.mTimestamp, new Callback() { + @Override + public void success(ScrobbleInfo scrobbleInfo, Response response) { + + } + + @Override + public void failure(RetrofitError error) { + + } + }); + } + + public void logout(){ + this.mUserSession.mToken = null; + this.mUserSession.mUsername = null; + this.mUserSession.update(context); + } + public String getUsername(){ + if(mUserSession!=null)return mUserSession.mUsername; + return null; + } } diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java new file mode 100644 index 000000000..2975c63ba --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java @@ -0,0 +1,26 @@ +package com.naman14.timber.lastfmapi; + +import com.naman14.timber.lastfmapi.models.ScrobbleInfo; +import com.naman14.timber.lastfmapi.models.UserLoginInfo; + +import retrofit.Callback; +import retrofit.http.Field; +import retrofit.http.FormUrlEncoded; +import retrofit.http.POST; + +/** + * Created by christoph on 17.07.16. + */ +public interface LastFmUserRestService { + + String BASE = "/"; + + @POST(BASE) + @FormUrlEncoded + void getUserLoginInfo(@Field("method") String method, @Field("format") String format, @Field("api_key") String apikey, @Field("api_sig") String apisig, @Field("username") String username, @Field("password") String password, Callback callback); + + @POST(BASE) + @FormUrlEncoded + void getScrobbleInfo(@Field("method") String method, @Field("api_key") String apikey, @Field("api_sig") String apisig, @Field("sk") String token,@Field("artist") String artist, @Field("track") String track, @Field("timestamp") long timestamp, Callback callback); + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java b/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java index 297cea1ad..5a932327e 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java @@ -17,10 +17,16 @@ import android.content.Context; import com.squareup.okhttp.Cache; +import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Response; +import java.io.IOException; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import okio.BufferedSink; +import okio.DeflaterSink; import retrofit.RequestInterceptor; import retrofit.RestAdapter; import retrofit.client.OkClient; @@ -29,7 +35,7 @@ public class RestServiceFactory { private static final String TAG_OK_HTTP = "OkHttp"; private static final long CACHE_SIZE = 1024 * 1024; - public static T create(final Context context, String baseUrl, Class clazz) { + public static T createStatic(final Context context, String baseUrl, Class clazz) { final OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setCache(new Cache(context.getApplicationContext().getCacheDir(), @@ -56,5 +62,16 @@ public void intercept(RequestFacade request) { } + public static T create(final Context context, String baseUrl, Class clazz) { + + RestAdapter.Builder builder = new RestAdapter.Builder() + .setEndpoint(baseUrl); + + return builder + .build() + .create(clazz); + + } + } diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java new file mode 100644 index 000000000..4c9949aa3 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java @@ -0,0 +1,13 @@ +package com.naman14.timber.lastfmapi.callbacks; + + + +/** + * Created by christoph on 17.07.16. + */ +public interface UserListener { + void userSuccess(); + + void userInfoFailed(); + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java new file mode 100644 index 000000000..f99841581 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java @@ -0,0 +1,43 @@ +package com.naman14.timber.lastfmapi.models; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by christoph on 17.07.16. + */ +public class LastfmUserSession { + private static final String USERNAME = "name"; + private static final String TOKEN = "key"; + + private static final String PREFERENCES_NAME = "Lastfm"; + + public static LastfmUserSession getSession(Context context) { + SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + LastfmUserSession session = new LastfmUserSession(); + session.mToken = preferences.getString(TOKEN, null); + session.mUsername = preferences.getString(USERNAME, null); + if (session.mToken == null || session.mUsername == null) return null; + return session; + } + + public void update(Context context) { + SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (this.mToken == null || this.mUsername == null) { + editor.clear(); + } else { + editor.putString(TOKEN, this.mToken); + editor.putString(USERNAME, this.mUsername); + } + editor.commit(); + } + + @SerializedName(USERNAME) + public String mUsername; + + @SerializedName(TOKEN) + public String mToken; +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java new file mode 100644 index 000000000..1db5a066d --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java @@ -0,0 +1,7 @@ +package com.naman14.timber.lastfmapi.models; + +/** + * Created by christoph on 17.07.16. + */ +public class ScrobbleInfo { +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java new file mode 100644 index 000000000..2d4ff9636 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java @@ -0,0 +1,34 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class ScrobbleQuery { + private static final String ARTIST_NAME = "artist"; + private static final String TRACK_NAME = "track"; + private static final String TIMESTAMP_NAME = "timestamp"; + + @SerializedName(ARTIST_NAME) + public String mArtist; + + @SerializedName(TRACK_NAME) + public String mTrack; + + @SerializedName(TIMESTAMP_NAME) + public long mTimestamp; + + public static final String Method = "track.scrobble"; + + public ScrobbleQuery(String artist, String track, long timestamp) { + this.mArtist = artist; + this.mTrack = track; + this.mTimestamp = timestamp; + } + + public String getSignature(String token) { + return "api_key" + LastFmClient.API_KEY + ARTIST_NAME + this.mArtist + "method" + Method + "sk" + token + TIMESTAMP_NAME + this.mTimestamp + TRACK_NAME + this.mTrack + LastFmClient.API_SECRET; + } +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java new file mode 100644 index 000000000..41a601e84 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java @@ -0,0 +1,16 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class UserLoginInfo { + private static final String SESSION = "session"; + + @SerializedName(SESSION) + public LastfmUserSession mSession; + + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java new file mode 100644 index 000000000..8f8f9fba1 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java @@ -0,0 +1,29 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class UserLoginQuery { + private static final String USERNAME_NAME = "username"; + private static final String PASSWORD_NAME = "password"; + + @SerializedName(USERNAME_NAME) + public String mUsername; + + @SerializedName(PASSWORD_NAME) + public String mPassword; + + public static final String Method = "auth.getMobileSession"; + + public UserLoginQuery(String username, String password) { + this.mUsername = username; + this.mPassword = password; + } + + public String getSignature() { + return "api_key" + LastFmClient.API_KEY + "method" + Method + "password" + mPassword + "username" + mUsername + LastFmClient.API_SECRET; + } +} diff --git a/app/src/main/res/layout/dialog_lastfm_login.xml b/app/src/main/res/layout/dialog_lastfm_login.xml new file mode 100644 index 000000000..5b2b192ca --- /dev/null +++ b/app/src/main/res/layout/dialog_lastfm_login.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index dee83489a..28abb02ff 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -41,8 +41,8 @@ %1$s %2$s %2$d:%3$02d %1$d:%2$02d:%3$02d - Laporkan bug di sini - Spesifikasikan permintaan fitur dalam komunitas Google+ di sini + Laporkan bug di sini + Spesifikasikan permintaan fitur dalam komunitas Google+ di sini Lihat Sebagai Daftar diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8017423a4..a97571750 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -112,4 +112,14 @@ app:ateKey_pref_checkBox="?ate_key" /> + + + + + + + \ No newline at end of file From d2b0c960b11d193c3da873fcb5a07118a4af911c Mon Sep 17 00:00:00 2001 From: naman14 Date: Sat, 29 Oct 2016 07:42:02 +0530 Subject: [PATCH 041/224] fix build errors, update app-theme-engine --- app/build.gradle | 30 ++++++------ .../timber/widgets/PopupImageView.java | 44 ++++++++++++++++++ .../drawable-hdpi/ic_more_vert_black_24dp.png | Bin 0 -> 132 bytes .../drawable-mdpi/ic_more_vert_black_24dp.png | Bin 0 -> 108 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 155 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 205 bytes .../ic_more_vert_black_24dp.png | Bin 0 -> 272 bytes app/src/main/res/layout/item_album_song.xml | 4 +- app/src/main/res/layout/item_artist_song.xml | 4 +- .../main/res/layout/item_playing_queue.xml | 4 +- app/src/main/res/layout/item_song.xml | 5 +- .../main/res/layout/item_song_playlist.xml | 4 +- app/src/main/res/layout/item_song_timber1.xml | 4 +- app/src/main/res/values-id/strings.xml | 4 +- 14 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/com/naman14/timber/widgets/PopupImageView.java create mode 100644 app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png diff --git a/app/build.gradle b/app/build.gradle index 2ec91afed..f7516f469 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileSdkVersion 24 + buildToolsVersion '24.0.3' defaultConfig { applicationId "naman14.timber" minSdkVersion 16 - targetSdkVersion 23 + targetSdkVersion 24 versionCode 9 versionName "0.15b" //renderscript support mode is not supported for 21+ with gradle version 2.0 @@ -38,12 +38,12 @@ repositories { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:recyclerview-v7:23.3.0' - compile 'com.android.support:cardview-v7:23.3.0' - compile 'com.android.support:palette-v7:23.3.0' - compile 'com.android.support:design:23.3.0' - compile 'com.android.support:percent:23.3.0' + compile 'com.android.support:appcompat-v7:24.2.1' + compile 'com.android.support:recyclerview-v7:24.2.1' + compile 'com.android.support:cardview-v7:24.2.1' + compile 'com.android.support:palette-v7:24.2.1' + compile 'com.android.support:design:24.2.1' + compile 'com.android.support:percent:24.2.1' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4' compile 'net.steamcrafted:materialiconlib:1.0.3' @@ -53,13 +53,11 @@ dependencies { compile 'com.google.code.gson:gson:2.3' compile 'de.Maxr1998:track-selector-lib:1.1' - compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') { - transitive = true - } - compile('com.github.naman14:app-theme-engine:0.5.1@aar') { - transitive = true - } - compile('com.github.afollestad.material-dialogs:commons:0.8.5.3@aar') { + compile 'com.afollestad.material-dialogs:core:0.9.0.2' + compile 'com.afollestad.material-dialogs:commons:0.9.0.2' + + compile('com.github.naman14:app-theme-engine:0.5.2@aar') { transitive = true } + } diff --git a/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java b/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java new file mode 100644 index 000000000..48fa7daee --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java @@ -0,0 +1,44 @@ +package com.naman14.timber.widgets; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.afollestad.appthemeengine.util.TintHelper; + +/** + * Created by naman on 29/10/16. + */ +public class PopupImageView extends ImageView { + + public PopupImageView(Context context) { + super(context); + tint(); + } + + public PopupImageView(Context context, AttributeSet attrs) { + super(context, attrs); + tint(); + } + + public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + tint(); + } + + @TargetApi(21) + public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + tint(); + } + + private void tint() { + if (PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("dark_theme", false)) { + TintHelper.setTint(this, Color.parseColor("#eeeeee")); + } else TintHelper.setTint(this, Color.parseColor("#434343")); + } + +} diff --git a/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..22acc550088d98f9d98ced517de75b5e8ead3c7c GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B5}8r;B4q1>@UGdwClSM3^70uIQ21 z47_&cJkx~*+Z%R2IKOxGVZM^fH+7^Mmwc{S?NKJ@tD@b{HffqWt7pJW9xm}`3bIR@ eOD6DrGLJi>8B(9~m^Tk-EQ6=3pUXO@geCy;;w$k0 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0e4f2f6ea0564fe9d6ebdb4bea0df48aced9de0f GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1V^0^ykP60RiERoU4^0niv(+!U z$~^Ty`=RMavMX4xeQ2uS{l}xPM$^=S>$DV@#Ph3 zmm-(IJxlAmHdKggydBZHA?LrN(f;zraK2yFla}<&ocsFTF&FO*>g+$WJLfsbvhR#o zqU5?6`INL2k&sND<*?!p=%CFlpb5%Rrp)D#!8^w7WtAYxjJ8qaZaq8!Lha3*C xpO9i0)g!jJUB|8aV$u`{=V%64OvRJ&URzP%pP$_?yn*gx@O1TaS?83{1OO~yQ~m$| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4642a3b66e690a25544c7a65b5484607c76e9e4d GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgu6w#ThEy=Vz2TVE>>%O(aeKF| z&%}AAoaYX07I~@2Qzf){=E~b*b6q_X?JD-aXZn{o>8SH070*d=@0yRCD&F7PI4|Iy zgr>dT{)s<*uuMbP?bJzL!+Uj>du1MOpP(^y_vJlXtsXdjo_CdTHuJx`L9zyy z_bjw};Pttx=xP2JS*8CI?{O?lw)*qOUytSD#5&t0XL`98ez{Y*;jMY`-`}^VtDMwJ z5#Pjpv-OQrnIiiQFtvL^tj8o3AbA2TH#1Zw0j|Uo=)7NS4apHKER!-ifL>(qboFyt I=akR{05Df}#Q*>R literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/item_album_song.xml b/app/src/main/res/layout/item_album_song.xml index 310bc6d86..4326ccd0f 100644 --- a/app/src/main/res/layout/item_album_song.xml +++ b/app/src/main/res/layout/item_album_song.xml @@ -71,7 +71,7 @@ app:materialIconSize="35dp" /> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_artist_song.xml b/app/src/main/res/layout/item_artist_song.xml index f8c8277e8..92d75979f 100644 --- a/app/src/main/res/layout/item_artist_song.xml +++ b/app/src/main/res/layout/item_artist_song.xml @@ -67,7 +67,7 @@ android:visibility="invisible" android:layout_toLeftOf="@+id/popup_menu"/> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_playing_queue.xml b/app/src/main/res/layout/item_playing_queue.xml index 86d11200a..c0c86d2f1 100644 --- a/app/src/main/res/layout/item_playing_queue.xml +++ b/app/src/main/res/layout/item_playing_queue.xml @@ -75,7 +75,7 @@ android:layout_toLeftOf="@+id/popup_menu" android:visibility="gone" /> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_song.xml b/app/src/main/res/layout/item_song.xml index 342d4c76c..46c980098 100644 --- a/app/src/main/res/layout/item_song.xml +++ b/app/src/main/res/layout/item_song.xml @@ -64,7 +64,7 @@ android:visibility="gone" /> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_song_playlist.xml b/app/src/main/res/layout/item_song_playlist.xml index af60a39e7..7368d2801 100644 --- a/app/src/main/res/layout/item_song_playlist.xml +++ b/app/src/main/res/layout/item_song_playlist.xml @@ -63,7 +63,7 @@ android:layout_toLeftOf="@+id/popup_menu" android:visibility="gone" /> - diff --git a/app/src/main/res/layout/item_song_timber1.xml b/app/src/main/res/layout/item_song_timber1.xml index ab213a9fd..f1f69629a 100644 --- a/app/src/main/res/layout/item_song_timber1.xml +++ b/app/src/main/res/layout/item_song_timber1.xml @@ -64,7 +64,7 @@ android:visibility="gone" /> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index dee83489a..28abb02ff 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -41,8 +41,8 @@ %1$s %2$s %2$d:%3$02d %1$d:%2$02d:%3$02d - Laporkan bug di sini - Spesifikasikan permintaan fitur dalam komunitas Google+ di sini + Laporkan bug di sini + Spesifikasikan permintaan fitur dalam komunitas Google+ di sini Lihat Sebagai Daftar From 13abe0332e8425aeb6eda2aca6c87409736c9c39 Mon Sep 17 00:00:00 2001 From: naman14 Date: Sat, 29 Oct 2016 07:55:37 +0530 Subject: [PATCH 042/224] update travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b7ffa368..b94e59548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,6 @@ android: components: - platform-tools - tools - - build-tools-23.0.2 - - android-23 + - build-tools-24.0.3 + - android-24 - extra-android-m2repository \ No newline at end of file From 57668194621dd1d044877da384d57691e37e3ab4 Mon Sep 17 00:00:00 2001 From: naman14 Date: Sat, 29 Oct 2016 09:39:10 +0530 Subject: [PATCH 043/224] add crashlytics --- .gitignore | 3 ++ app/build.gradle | 5 +++ app/src/main/AndroidManifest.xml | 39 +++++++++++------- app/src/main/ic_launcher-web.png | Bin 39891 -> 0 bytes .../java/com/naman14/timber/TimberApp.java | 4 ++ build.gradle | 2 + 6 files changed, 38 insertions(+), 15 deletions(-) delete mode 100644 app/src/main/ic_launcher-web.png diff --git a/.gitignore b/.gitignore index 1edd30fff..8e1707a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ /app/app-release.apk app/app.iml app/manifest-merger-release-report.txt + +secret.xml +google-services.json diff --git a/app/build.gradle b/app/build.gradle index f7516f469..261fb0e5f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'io.fabric' android { compileSdkVersion 24 @@ -34,6 +35,7 @@ android { repositories { jcenter() + maven { url 'https://maven.fabric.io/public' } } dependencies { @@ -59,5 +61,8 @@ dependencies { compile('com.github.naman14:app-theme-engine:0.5.2@aar') { transitive = true } + compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { + transitive = true; + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed88db954..919bb6d4f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,32 +31,36 @@ - - - - - + + + + + + + + - - - - + + + + - + + + - - - - + + + + @@ -96,6 +100,11 @@ + + + diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png deleted file mode 100644 index 8c3b0ffe86b30dcd12d4b735ce1451909245218a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39891 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 z�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 z Date: Sat, 29 Oct 2016 10:01:38 +0530 Subject: [PATCH 044/224] add iap aidl --- .../vending/billing/IInAppBillingService.aidl | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl diff --git a/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 000000000..2a492f784 --- /dev/null +++ b/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.billing; + +import android.os.Bundle; + +/** + * InAppBillingService is the service that provides in-app billing version 3 and beyond. + * This service provides the following features: + * 1. Provides a new API to get details of in-app items published for the app including + * price, type, title and description. + * 2. The purchase flow is synchronous and purchase information is available immediately + * after it completes. + * 3. Purchase information of in-app purchases is maintained within the Google Play system + * till the purchase is consumed. + * 4. An API to consume a purchase of an inapp item. All purchases of one-time + * in-app items are consumable and thereafter can be purchased again. + * 5. An API to get current purchases of the user immediately. This will not contain any + * consumed purchases. + * + * All calls will give a response code with the following possible values + * RESULT_OK = 0 - success + * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog + * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested + * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase + * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API + * RESULT_ERROR = 6 - Fatal error during the API action + * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned + * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned + */ +interface IInAppBillingService { + /** + * Checks support for the requested billing API version, package and in-app type. + * Minimum API version supported by this interface is 3. + * @param apiVersion the billing version which the app is using + * @param packageName the package name of the calling app + * @param type type of the in-app item being purchased "inapp" for one-time purchases + * and "subs" for subscription. + * @return RESULT_OK(0) on success, corresponding result code on failures + */ + int isBillingSupported(int apiVersion, String packageName, String type); + + /** + * Provides details of a list of SKUs + * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle + * with a list JSON strings containing the productId, price, title and description. + * This API can be called with a maximum of 20 SKUs. + * @param apiVersion billing API version that the Third-party is using + * @param packageName the package name of the calling app + * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "DETAILS_LIST" with a StringArrayList containing purchase information + * in JSON format similar to: + * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", + * "title : "Example Title", "description" : "This is an example description" }' + */ + Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); + + /** + * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, + * the type, a unique purchase token and an optional developer payload. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type the type of the in-app item ("inapp" for one-time purchases + * and "subs" for subscription). + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "BUY_INTENT" - PendingIntent to start the purchase flow + * + * The Pending intent should be launched with startIntentSenderForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + * TODO: change this to app-specific keys. + */ + Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, + String developerPayload); + + /** + * Returns the current SKUs owned by the user of the type and package name specified along with + * purchase information and a signature of the data to be validated. + * This will return all SKUs that have been purchased in V3 and managed items purchased using + * V1 and V2 that have not been consumed. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param type the type of the in-app items being requested + * ("inapp" for one-time purchases and "subs" for subscription). + * @param continuationToken to be set as null for the first call, if the number of owned + * skus are too many, a continuationToken is returned in the response bundle. + * This method can be called again with the continuation token to get the next set of + * owned skus. + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs + * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information + * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures + * of the purchase information + * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the + * next set of in-app purchases. Only set if the + * user has more owned skus than the current list. + */ + Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); + + /** + * Consume the last purchase of the given SKU. This will result in this item being removed + * from all subsequent responses to getPurchases() and allow re-purchase of this item. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param purchaseToken token in the purchase information JSON that identifies the purchase + * to be consumed + * @return 0 if consumption succeeded. Appropriate error values for failures. + */ + int consumePurchase(int apiVersion, String packageName, String purchaseToken); +} From 771429b77f748bafd4d58c2b80a664d1f86bc920 Mon Sep 17 00:00:00 2001 From: naman14 Date: Sat, 29 Oct 2016 10:06:05 +0530 Subject: [PATCH 045/224] disable crashlytics for debug builds --- app/build.gradle | 1 + app/src/main/java/com/naman14/timber/TimberApp.java | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 261fb0e5f..ef262a443 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { + ext.enableCrashlytics = false versionNameSuffix "-debug" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' diff --git a/app/src/main/java/com/naman14/timber/TimberApp.java b/app/src/main/java/com/naman14/timber/TimberApp.java index 381971dec..0a8e6118d 100644 --- a/app/src/main/java/com/naman14/timber/TimberApp.java +++ b/app/src/main/java/com/naman14/timber/TimberApp.java @@ -18,6 +18,7 @@ import com.afollestad.appthemeengine.ATE; import com.crashlytics.android.Crashlytics; +import com.crashlytics.android.core.CrashlyticsCore; import com.naman14.timber.permissions.Nammu; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; @@ -38,7 +39,13 @@ public static synchronized TimberApp getInstance() { public void onCreate() { super.onCreate(); mInstance = this; - Fabric.with(this, new Crashlytics()); + + //disable crashlytics for debug builds + Crashlytics crashlyticsKit = new Crashlytics.Builder() + .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()) + .build(); + Fabric.with(this, crashlyticsKit); + ImageLoaderConfiguration localImageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this).build(); ImageLoader.getInstance().init(localImageLoaderConfiguration); L.writeLogs(false); From b82de3efd78994c9b133815725f63be984af8348 Mon Sep 17 00:00:00 2001 From: naman14 Date: Sat, 29 Oct 2016 12:53:08 +0530 Subject: [PATCH 046/224] basic iab implementation --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 4 + .../vending/billing/IInAppBillingService.aidl | 144 ------------------ .../timber/activities/DonateActivity.java | 130 ++++++++++++++++ .../timber/activities/MainActivity.java | 8 + app/src/main/res/layout/activity_donate.xml | 17 +++ .../main/res/layout/item_donate_product.xml | 11 ++ app/src/main/res/menu/drawer_view.xml | 4 + 8 files changed, 178 insertions(+), 146 deletions(-) delete mode 100644 app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl create mode 100644 app/src/main/java/com/naman14/timber/activities/DonateActivity.java create mode 100644 app/src/main/res/layout/activity_donate.xml create mode 100644 app/src/main/res/layout/item_donate_product.xml diff --git a/app/build.gradle b/app/build.gradle index ef262a443..cb56c503f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "naman14.timber" minSdkVersion 16 targetSdkVersion 24 - versionCode 9 - versionName "0.15b" + versionCode 10 + versionName "0.16b" //renderscript support mode is not supported for 21+ with gradle version 2.0 renderscriptTargetApi 20 renderscriptSupportModeEnabled true @@ -65,5 +65,7 @@ dependencies { compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true; } + compile 'com.anjlab.android.iab.v3:library:1.0.+' + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 919bb6d4f..f2d7919ed 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ + + + + diff --git a/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl deleted file mode 100644 index 2a492f784..000000000 --- a/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.billing; - -import android.os.Bundle; - -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog - * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -interface IInAppBillingService { - /** - * Checks support for the requested billing API version, package and in-app type. - * Minimum API version supported by this interface is 3. - * @param apiVersion the billing version which the app is using - * @param packageName the package name of the calling app - * @param type type of the in-app item being purchased "inapp" for one-time purchases - * and "subs" for subscription. - * @return RESULT_OK(0) on success, corresponding result code on failures - */ - int isBillingSupported(int apiVersion, String packageName, String type); - - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the Third-party is using - * @param packageName the package name of the calling app - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", - * "title : "Example Title", "description" : "This is an example description" }' - */ - Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type the type of the in-app item ("inapp" for one-time purchases - * and "subs" for subscription). - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - * TODO: change this to app-specific keys. - */ - Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, - String developerPayload); - - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type the type of the in-app items being requested - * ("inapp" for one-time purchases and "subs" for subscription). - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); - - /** - * Consume the last purchase of the given SKU. This will result in this item being removed - * from all subsequent responses to getPurchases() and allow re-purchase of this item. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param purchaseToken token in the purchase information JSON that identifies the purchase - * to be consumed - * @return 0 if consumption succeeded. Appropriate error values for failures. - */ - int consumePurchase(int apiVersion, String packageName, String purchaseToken); -} diff --git a/app/src/main/java/com/naman14/timber/activities/DonateActivity.java b/app/src/main/java/com/naman14/timber/activities/DonateActivity.java new file mode 100644 index 000000000..7684952ab --- /dev/null +++ b/app/src/main/java/com/naman14/timber/activities/DonateActivity.java @@ -0,0 +1,130 @@ +package com.naman14.timber.activities; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.SkuDetails; +import com.anjlab.android.iab.v3.TransactionDetails; +import com.naman14.timber.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by naman on 29/10/16. + */ +public class DonateActivity extends BaseThemedActivity implements BillingProcessor.IBillingHandler { + + private static final String DONATION_1 = "naman14.timber.donate_1"; + private static final String DONATION_2 = "naman14.timber.donate_2"; + private static final String DONATION_3 = "naman14.timber.donate_3"; + private static final String DONATION_5 = "naman14.timber.donate_5"; + private static final String DONATION_10 = "naman14.timber.donate_10"; + + private boolean readyToPurchase = false; + BillingProcessor bp; + + private LinearLayout productListView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_donate); + + bp = new BillingProcessor(this, getString(R.string.play_billing_license_key), this); + + productListView = (LinearLayout) findViewById(R.id.product_list); + + ((Button) findViewById(R.id.btn_donate)).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (readyToPurchase) + bp.purchase(DonateActivity.this, DONATION_1); + else + Toast.makeText(DonateActivity.this, "Not initialised", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onBillingInitialized() { + readyToPurchase = true; + getProducts(); + } + + @Override + public void onProductPurchased(String productId, TransactionDetails details) { + + } + + @Override + public void onBillingError(int errorCode, Throwable error) { + + } + + @Override + public void onPurchaseHistoryRestored() { + + } + + @Override + public void onDestroy() { + if (bp != null) + bp.release(); + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!bp.handleActivityResult(requestCode, resultCode, data)) + super.onActivityResult(requestCode, resultCode, data); + } + + private void getProducts() { + + new AsyncTask>() { + @Override + protected List doInBackground(Void... voids) { + ArrayList products = new ArrayList<>(); + + products.add(DONATION_1); + products.add(DONATION_2); + products.add(DONATION_3); + products.add(DONATION_5); + products.add(DONATION_10); + + return bp.getPurchaseListingDetails(products); + } + + @Override + protected void onPostExecute(List productList) { + super.onPostExecute(productList); + for (int i = 0; i < productList.size(); i++) { + final SkuDetails product = productList.get(i); + View rootView = LayoutInflater.from(DonateActivity.this).inflate(R.layout.item_donate_product, productListView, false); + + TextView detail = (TextView) rootView.findViewById(R.id.product_detail); + detail.setText(product.title); + + rootView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + bp.purchase(DonateActivity.this, product.productId); + } + }); + productListView.addView(rootView); + + } + } + }.execute(); + } + +} diff --git a/app/src/main/java/com/naman14/timber/activities/MainActivity.java b/app/src/main/java/com/naman14/timber/activities/MainActivity.java index 4c000f53f..2fdc91d6d 100644 --- a/app/src/main/java/com/naman14/timber/activities/MainActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/MainActivity.java @@ -34,6 +34,7 @@ import android.widget.TextView; import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer; +import com.anjlab.android.iab.v3.BillingProcessor; import com.naman14.timber.MusicPlayer; import com.naman14.timber.R; import com.naman14.timber.fragments.AlbumDetailFragment; @@ -196,6 +197,10 @@ public void run() { } }, 350); } + + if(!BillingProcessor.isIabServiceAvailable(this)) { + navigationView.getMenu().removeItem(R.id.nav_donate); + } } private void loadEverything() { @@ -335,6 +340,9 @@ public void run() { } }, 350); + break; + case R.id.nav_donate: + startActivity(new Intent(MainActivity.this, DonateActivity.class)); break; } diff --git a/app/src/main/res/layout/activity_donate.xml b/app/src/main/res/layout/activity_donate.xml new file mode 100644 index 000000000..b3e43f416 --- /dev/null +++ b/app/src/main/res/layout/activity_donate.xml @@ -0,0 +1,17 @@ + + + + +