diff --git a/.gitignore b/.gitignore index 8a6b332..4e0d2e9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ /captures .externalNativeBuild .cxx - +/spotify-app-remote/build/ +/spotify-auth/build/ diff --git a/app/build.gradle b/app/build.gradle index 376a671..5710e63 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -68,7 +68,8 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" - implementation("com.squareup.okhttp3:okhttp:4.9.0") + implementation "com.squareup.okhttp3:okhttp:4.9.0" + implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.4.1' - implementation('com.ibm.watson:ibm-watson:8.6.3') + implementation 'com.ibm.watson:ibm-watson:8.6.3' } \ No newline at end of file diff --git a/app/src/main/java/com/archrahkshi/spotifine/data/Constants.kt b/app/src/main/java/com/archrahkshi/spotifine/data/Constants.kt index 84bfac9..e6757dc 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/data/Constants.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/data/Constants.kt @@ -5,7 +5,6 @@ const val ALBUM_FROM_PLAYLIST_DISTINCTION = 5 const val ALBUMS = "albums" const val ARTIST_FROM_ME_DISTINCTION = 2 const val ARTISTS = "artists" -const val CLIENT_ID = "fbe0ec189f0247f99909e75530bac38e" const val DURATION = "duration" const val FROM_ARTIST = "from_artist" const val FROM_ME = "from_me" @@ -15,6 +14,10 @@ const val LIST_TYPE = "list_type" const val NAME = "name" const val ONE_DIGIT = 9 const val PLAYLISTS = "playlists" -const val REDIRECT_URI = "http://localhost:8888/callback/" -const val REQUEST_CODE = 1337 +const val SPOTIFY_CLIENT_ID = "fbe0ec189f0247f99909e75530bac38e" +const val SPOTIFY_REDIRECT_URI = "http://localhost:8888/callback/" +const val SPOTIFY_REQUEST_CODE = 1337 const val URL = "url" +const val TRANSLATOR_API_KEY = "Nbo7_ne4t1F0JTjcmCS_06yGOuJNdEc7efawvtOHS9i_" +const val TRANSLATOR_URL = "https://api.eu-gb.language-translator.watson.cloud.ibm.com" +const val TRANSLATOR_VERSION = "2018-05-01" diff --git a/app/src/main/java/com/archrahkshi/spotifine/data/TracksAdapter.kt b/app/src/main/java/com/archrahkshi/spotifine/data/TracksAdapter.kt index a63d20f..9e06a2b 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/data/TracksAdapter.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/data/TracksAdapter.kt @@ -40,9 +40,9 @@ class TracksAdapter( textViewTrackDuration.text = track.duration.milliseconds.toComponents { HH, mm, ss, _ -> var duration = "" if (HH > 0) duration += "$HH:" - if (duration.isNotEmpty() && mm < ONE_DIGIT) duration += '0' + if (duration.isNotEmpty() && mm <= ONE_DIGIT) duration += '0' duration += "$mm:" - if (duration.isNotEmpty() && ss < ONE_DIGIT) duration += '0' + if (duration.isNotEmpty() && ss <= ONE_DIGIT) duration += '0' "$duration$ss" } layoutItemList.setOnClickListener { clickListener(track) } diff --git a/app/src/main/java/com/archrahkshi/spotifine/ui/LyricsFragment.kt b/app/src/main/java/com/archrahkshi/spotifine/ui/LyricsFragment.kt index a2b6681..615f099 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/ui/LyricsFragment.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/ui/LyricsFragment.kt @@ -7,10 +7,26 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import com.archrahkshi.spotifine.R import com.archrahkshi.spotifine.data.LyricsAdapter +import com.archrahkshi.spotifine.data.TRANSLATOR_API_KEY +import com.archrahkshi.spotifine.data.TRANSLATOR_URL +import com.archrahkshi.spotifine.data.TRANSLATOR_VERSION +import com.ibm.cloud.sdk.core.security.IamAuthenticator +import com.ibm.watson.language_translator.v3.LanguageTranslator +import com.ibm.watson.language_translator.v3.model.TranslateOptions +import com.ibm.watson.language_translator.v3.util.Language.RUSSIAN import kotlinx.android.synthetic.main.fragment_lyrics.buttonTranslate import kotlinx.android.synthetic.main.fragment_lyrics.recyclerViewLyrics +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.Locale +import kotlin.coroutines.CoroutineContext -class LyricsFragment(private var isLyricsTranslated: Boolean) : Fragment() { +class LyricsFragment( + private val isLyricsTranslated: Boolean, + override val coroutineContext: CoroutineContext = Dispatchers.Main.immediate +) : Fragment(), CoroutineScope { override fun onCreateView( inflater: LayoutInflater, @@ -21,46 +37,105 @@ class LyricsFragment(private var isLyricsTranslated: Boolean) : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val lyricsOriginal = listOf( - "Hoort u mij toe", - "Als ik u verhaal", - "Van oude sagen", - "Van reuzentijd", - "", - "Vertel ons de sagen", - "Van oeroude machten", - "In 't Gelderse land", - "Wat weet u nog meer?", - ) - val lyricsTranslated = listOf( - "Послушай меня", - "Если я скажу тебе", - "Из старых саг", - "Из гигантских времен", - "", - "Расскажи нам саги", - "Древних сил", - "В стране Гелдерланд", - "Что еще ты знаешь?", - ) - val lyrics = if (isLyricsTranslated) lyricsTranslated else lyricsOriginal + launch { + val originalLyrics = getOriginalLyrics() + if (isLyricsTranslated) { + val (detectedLanguage, translatedLyrics) = getTranslatedLyrics(originalLyrics) + val appLocale = Locale(RUSSIAN) + buttonTranslate.text = getString( + R.string.detected_language, + Locale(detectedLanguage).getDisplayLanguage(appLocale), + appLocale.getDisplayLanguage(appLocale) + ) + recyclerViewLyrics.adapter = LyricsAdapter(translatedLyrics.split('\n')) + } else { + buttonTranslate.text = getString(R.string.translate) + recyclerViewLyrics.adapter = LyricsAdapter(originalLyrics.split('\n')) + } - val detectedLanguage = "..." // TODO - val toRussian = "$detectedLanguage > ${resources.getString(R.string.russian)}" - buttonTranslate.text = - if (isLyricsTranslated) - toRussian - else - resources.getString(R.string.translate) - - buttonTranslate.setOnClickListener { - isLyricsTranslated = !isLyricsTranslated - fragmentManager?.beginTransaction()?.replace( - R.id.frameLayoutPlayer, - LyricsFragment(isLyricsTranslated) - )?.commit() + buttonTranslate.setOnClickListener { + fragmentManager?.beginTransaction()?.replace( + R.id.frameLayoutPlayer, + LyricsFragment(!isLyricsTranslated) + )?.commit() + } } + } - recyclerViewLyrics.adapter = LyricsAdapter(lyrics) + private suspend fun getOriginalLyrics() = withContext(Dispatchers.IO) { + """ + Flying over darkened skies the battle will call + Distant angels crying in the eye of the storm + And the world falls under the starlight shining from heavens below + Long years of pain and sorrow searching for more + Cry for the touch of angels never before + And the stars fall on the horizon onwards and up through the pain + + Ride the wind and fight the demon steel shining bright + Standing together forever onwards flames burning strong + Hot wind in hell of pain and sorrow now and ever onwards + We stare into the dawn of a new world + + [Pre-Chorus] + Cry out for the fallen heroes + Lost in time ago + In our minds they still belong + When the sands of time are gone + + [Chorus] + Rise over shadow mountains blazing with power + Crossing valleys endless tears in unity we stand + Far and wide across the land the victory is ours + On towards the gates of reason + Fight for the truth and the freedom + Gloria + + Searching through the memories to open the door + Living on the edge of life like never before + And the ground chants under the moonlight facing their fears all the same + + Heavens fear now open wide and up for the the call + All in stark reality the angels will fall + And the world cries out for the silence lost in the voices unknown + + Blinded by the force of evil cries into the night + Never before have they seen the darkness now they are all gone + Out from the shadows storming on the wings of revelations + Your soul will feel no mercy come the dawn + + Hold on for the morning after + Never to let go + In the fire's burning strong + When the tides of time roll on + + [Chorus] + + [Pre-Chorus] + + [Chorus] + """.trimIndent() + } + + private suspend fun getTranslatedLyrics( + lyricsOriginal: String + ) = withContext(Dispatchers.IO) { + val result = LanguageTranslator( + TRANSLATOR_VERSION, + IamAuthenticator(TRANSLATOR_API_KEY) + ).apply { + serviceUrl = TRANSLATOR_URL + }.translate( + TranslateOptions.Builder() + // Line separators must be doubled for the lyrics to be translated line by line, + // not as a uniform text + .addText(lyricsOriginal.replace("\n", "\n\n")) + .target(RUSSIAN) + .build() + ).execute().result + Pair( + result.detectedLanguage, + // Returning to the original line separators + result.translations.first().translation.replace("\n\n", "\n") + ) } } diff --git a/app/src/main/java/com/archrahkshi/spotifine/ui/MainActivity.kt b/app/src/main/java/com/archrahkshi/spotifine/ui/MainActivity.kt index 30edcfc..5f83d60 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/ui/MainActivity.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/ui/MainActivity.kt @@ -6,9 +6,9 @@ import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.archrahkshi.spotifine.R import com.archrahkshi.spotifine.data.ACCESS_TOKEN -import com.archrahkshi.spotifine.data.CLIENT_ID -import com.archrahkshi.spotifine.data.REDIRECT_URI -import com.archrahkshi.spotifine.data.REQUEST_CODE +import com.archrahkshi.spotifine.data.SPOTIFY_CLIENT_ID +import com.archrahkshi.spotifine.data.SPOTIFY_REDIRECT_URI +import com.archrahkshi.spotifine.data.SPOTIFY_REQUEST_CODE import com.spotify.sdk.android.auth.AuthorizationClient import com.spotify.sdk.android.auth.AuthorizationRequest import com.spotify.sdk.android.auth.AuthorizationResponse @@ -21,11 +21,11 @@ class MainActivity : AppCompatActivity() { AuthorizationClient.openLoginActivity( this, - REQUEST_CODE, + SPOTIFY_REQUEST_CODE, AuthorizationRequest.Builder( - CLIENT_ID, + SPOTIFY_CLIENT_ID, AuthorizationResponse.Type.TOKEN, - REDIRECT_URI + SPOTIFY_REDIRECT_URI ).apply { setScopes(arrayOf("streaming", "user-library-read", "user-follow-read")) }.build() @@ -35,7 +35,7 @@ class MainActivity : AppCompatActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) - if (requestCode == REQUEST_CODE) { + if (requestCode == SPOTIFY_REQUEST_CODE) { val response: AuthorizationResponse? = AuthorizationClient.getResponse(resultCode, intent) when (response?.type) { diff --git a/app/src/main/java/com/archrahkshi/spotifine/ui/PlayerActivity.kt b/app/src/main/java/com/archrahkshi/spotifine/ui/PlayerActivity.kt index 3673f3e..69c38c6 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/ui/PlayerActivity.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/ui/PlayerActivity.kt @@ -1,16 +1,15 @@ package com.archrahkshi.spotifine.ui -import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import androidx.appcompat.app.AppCompatActivity import com.archrahkshi.spotifine.R -import com.archrahkshi.spotifine.data.CLIENT_ID import com.archrahkshi.spotifine.data.DURATION import com.archrahkshi.spotifine.data.ID -import com.archrahkshi.spotifine.data.REDIRECT_URI +import com.archrahkshi.spotifine.data.SPOTIFY_CLIENT_ID +import com.archrahkshi.spotifine.data.SPOTIFY_REDIRECT_URI import com.spotify.android.appremote.api.ConnectionParams import com.spotify.android.appremote.api.Connector import com.spotify.android.appremote.api.SpotifyAppRemote @@ -35,15 +34,14 @@ class PlayerActivity : AppCompatActivity() { val duration = intent.getLongExtra(DURATION, 0) Log.wtf("id", id?.toString()) Log.wtf("duration", duration.toString()) - val connectionParams = ConnectionParams.Builder(CLIENT_ID) - .setRedirectUri(REDIRECT_URI) + val connectionParams = ConnectionParams.Builder(SPOTIFY_CLIENT_ID) + .setRedirectUri(SPOTIFY_REDIRECT_URI) .showAuthView(true) .build() SpotifyAppRemote.connect( this, connectionParams, object : Connector.ConnectionListener { - @SuppressLint("SetTextI18n") override fun onConnected(spotifyAppRemote: SpotifyAppRemote) { this@PlayerActivity.pSpotifyAppRemote = spotifyAppRemote val appRemote = this@PlayerActivity.pSpotifyAppRemote!! @@ -52,7 +50,7 @@ class PlayerActivity : AppCompatActivity() { val seekBar = findViewById(R.id.seekBar) seekBar.max = duration.toInt() var flag = 0 - buttonPlay.text = "PLAY" + buttonPlay.text = getString(R.string.play) seekBar.setOnSeekBarChangeListener( object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { @@ -81,17 +79,17 @@ class PlayerActivity : AppCompatActivity() { 0 -> { appRemote.playerApi.play("spotify:track:$id") flag = 1 - buttonPlay.text = "PAUSE" + buttonPlay.text = getString(R.string.pause) } 1 -> { appRemote.playerApi.pause() flag = 2 - buttonPlay.text = "PLAY" + buttonPlay.text = getString(R.string.play) } 2 -> { appRemote.playerApi.resume() flag = 1 - buttonPlay.text = "PAUSE" + buttonPlay.text = getString(R.string.pause) } } } diff --git a/app/src/main/java/com/archrahkshi/spotifine/ui/TracksFragment.kt b/app/src/main/java/com/archrahkshi/spotifine/ui/TracksFragment.kt index 2db5661..c47bad7 100644 --- a/app/src/main/java/com/archrahkshi/spotifine/ui/TracksFragment.kt +++ b/app/src/main/java/com/archrahkshi/spotifine/ui/TracksFragment.kt @@ -57,7 +57,7 @@ class TracksFragment( recyclerViewTracks.adapter = TracksAdapter( createTrackLists(args?.getString(URL), args?.getString(ACCESS_TOKEN)) ) { - Log.i("Track", it.toString()) + Log.i("Track clicked", it.toString()) startActivity( Intent(activity, PlayerActivity::class.java).apply { putExtra(ID, it.id) diff --git a/app/src/main/res/layout/fragment_library_lists.xml b/app/src/main/res/layout/fragment_library_lists.xml index f82a0af..46119da 100644 --- a/app/src/main/res/layout/fragment_library_lists.xml +++ b/app/src/main/res/layout/fragment_library_lists.xml @@ -11,7 +11,10 @@ android:id="@+id/recyclerViewLists" android:layout_width="match_parent" android:layout_height="match_parent" + android:scrollbarThumbVertical="@android:color/white" + android:scrollbars="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:itemCount="20" tools:listitem="@layout/item_library_list" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_lyrics.xml b/app/src/main/res/layout/fragment_lyrics.xml index e2e35fa..ab87dfe 100644 --- a/app/src/main/res/layout/fragment_lyrics.xml +++ b/app/src/main/res/layout/fragment_lyrics.xml @@ -12,9 +12,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" + android:scrollbarThumbVertical="@android:color/white" + android:scrollbars="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + tools:itemCount="50" tools:listitem="@layout/item_lyrics_line" />