Remove Internet permission along with all Internet stuff
Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
This commit is contained in:
parent
b103387d73
commit
12bded682c
34 changed files with 18 additions and 2010 deletions
|
@ -88,11 +88,7 @@ dependencies {
|
|||
implementation "androidx.core:core-splashscreen:1.0.0-beta02"
|
||||
|
||||
implementation "com.google.android.material:material:$mdc_version"
|
||||
|
||||
def retrofit_version = '2.9.0'
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.7'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
|
||||
def material_dialog_version = "3.3.0"
|
||||
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
|
||||
|
@ -114,7 +110,6 @@ dependencies {
|
|||
def glide_version = '4.13.2'
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
|
||||
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
||||
|
||||
|
@ -125,7 +120,6 @@ dependencies {
|
|||
|
||||
implementation "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||
|
||||
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5'
|
||||
implementation 'com.github.Adonai:jaudiotagger:2.3.15'
|
||||
implementation 'com.r0adkll:slidableactivity:2.1.0'
|
||||
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
|
|
|
@ -18,20 +18,12 @@ import android.provider.BaseColumns
|
|||
import android.provider.MediaStore
|
||||
|
||||
object Constants {
|
||||
const val RATE_ON_GOOGLE_PLAY =
|
||||
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
|
||||
const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
|
||||
const val WEBSITE = "https://retromusic.app"
|
||||
const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro"
|
||||
const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel"
|
||||
const val USER_PROFILE = "profile.jpg"
|
||||
const val USER_BANNER = "banner.jpg"
|
||||
const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
|
||||
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
|
||||
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
|
||||
const val FAQ_LINK = "https://github.com/MuntashirAkon/Metro/blob/master/FAQ.md"
|
||||
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
|
||||
const val AUDIO_SCROBBLER_URL = "https://ws.audioscrobbler.com/2.0/"
|
||||
|
||||
const val IS_MUSIC =
|
||||
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
|
||||
|
@ -130,7 +122,6 @@ const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
|
|||
const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
|
||||
const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
|
||||
const val LAST_CHANGELOG_VERSION = "last_changelog_version"
|
||||
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
|
||||
const val START_DIRECTORY = "start_directory"
|
||||
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
|
||||
const val LOCK_SCREEN = "lock_screen"
|
||||
|
|
|
@ -14,10 +14,6 @@ import io.github.muntashirakon.music.fragments.artists.ArtistDetailsViewModel
|
|||
import io.github.muntashirakon.music.fragments.genres.GenreDetailsViewModel
|
||||
import io.github.muntashirakon.music.fragments.playlists.PlaylistDetailsViewModel
|
||||
import io.github.muntashirakon.music.model.Genre
|
||||
import io.github.muntashirakon.music.network.provideDefaultCache
|
||||
import io.github.muntashirakon.music.network.provideLastFmRest
|
||||
import io.github.muntashirakon.music.network.provideLastFmRetrofit
|
||||
import io.github.muntashirakon.music.network.provideOkHttp
|
||||
import io.github.muntashirakon.music.repository.*
|
||||
import io.github.muntashirakon.music.util.FilePathUtil
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
|
@ -28,22 +24,6 @@ import org.koin.androidx.viewmodel.dsl.viewModel
|
|||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
val networkModule = module {
|
||||
|
||||
factory {
|
||||
provideDefaultCache()
|
||||
}
|
||||
factory {
|
||||
provideOkHttp(get(), get())
|
||||
}
|
||||
single {
|
||||
provideLastFmRetrofit(get())
|
||||
}
|
||||
single {
|
||||
provideLastFmRest(get())
|
||||
}
|
||||
}
|
||||
|
||||
private val roomModule = module {
|
||||
|
||||
single {
|
||||
|
@ -117,7 +97,6 @@ private val dataModule = module {
|
|||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
)
|
||||
} bind Repository::class
|
||||
|
||||
|
@ -200,4 +179,4 @@ private val viewModules = module {
|
|||
}
|
||||
}
|
||||
|
||||
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, networkModule, roomModule)
|
||||
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, roomModule)
|
|
@ -19,53 +19,17 @@ import android.content.ClipboardManager
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.StringDef
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.appthemehelper.util.MaterialUtil
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.base.AbsThemeActivity
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.DeviceInfo
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.Report
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.github.ExtraInfo
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.github.GithubLogin
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.github.GithubTarget
|
||||
import io.github.muntashirakon.music.databinding.ActivityBugReportBinding
|
||||
import io.github.muntashirakon.music.extensions.accentColor
|
||||
import io.github.muntashirakon.music.extensions.setTaskDescriptionColorAuto
|
||||
import io.github.muntashirakon.music.extensions.showToast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.eclipse.egit.github.core.Issue
|
||||
import org.eclipse.egit.github.core.client.GitHubClient
|
||||
import org.eclipse.egit.github.core.client.RequestException
|
||||
import org.eclipse.egit.github.core.service.IssueService
|
||||
import java.io.IOException
|
||||
|
||||
private const val RESULT_SUCCESS = "RESULT_OK"
|
||||
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
|
||||
private const val RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN"
|
||||
private const val RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED"
|
||||
private const val RESULT_UNKNOWN = "RESULT_UNKNOWN"
|
||||
|
||||
@StringDef(
|
||||
RESULT_SUCCESS,
|
||||
RESULT_BAD_CREDENTIALS,
|
||||
RESULT_INVALID_TOKEN,
|
||||
RESULT_ISSUES_NOT_ENABLED,
|
||||
RESULT_UNKNOWN
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class Result
|
||||
|
||||
open class BugReportActivity : AbsThemeActivity() {
|
||||
|
||||
|
@ -91,72 +55,20 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
setSupportActionBar(binding.toolbar)
|
||||
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
TintHelper.setTintAuto(binding.cardReport.optionUseAccount, accentColor, false)
|
||||
binding.cardReport.optionUseAccount.setOnClickListener {
|
||||
binding.cardReport.inputTitle.isEnabled = true
|
||||
binding.cardReport.inputDescription.isEnabled = true
|
||||
binding.cardReport.inputUsername.isEnabled = true
|
||||
binding.cardReport.inputPassword.isEnabled = true
|
||||
|
||||
binding.cardReport.optionAnonymous.isChecked = false
|
||||
binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
|
||||
override fun onHidden(fab: FloatingActionButton?) {
|
||||
super.onHidden(fab)
|
||||
binding.sendFab.setImageResource(R.drawable.ic_send)
|
||||
binding.sendFab.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
TintHelper.setTintAuto(binding.cardReport.optionAnonymous, accentColor, false)
|
||||
binding.cardReport.optionAnonymous.setOnClickListener {
|
||||
binding.cardReport.inputTitle.isEnabled = false
|
||||
binding.cardReport.inputDescription.isEnabled = false
|
||||
binding.cardReport.inputUsername.isEnabled = false
|
||||
binding.cardReport.inputPassword.isEnabled = false
|
||||
|
||||
binding.cardReport.optionUseAccount.isChecked = false
|
||||
binding.sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
|
||||
override fun onHidden(fab: FloatingActionButton?) {
|
||||
super.onHidden(fab)
|
||||
binding.sendFab.setImageResource(R.drawable.ic_open_in_browser)
|
||||
binding.sendFab.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding.cardReport.inputPassword.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
reportIssue()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
|
||||
|
||||
TintHelper.setTintAuto(binding.sendFab, accentColor, true)
|
||||
binding.sendFab.setOnClickListener { reportIssue() }
|
||||
|
||||
MaterialUtil.setTint(binding.cardReport.inputLayoutTitle, false)
|
||||
MaterialUtil.setTint(binding.cardReport.inputLayoutDescription, false)
|
||||
MaterialUtil.setTint(binding.cardReport.inputLayoutUsername, false)
|
||||
MaterialUtil.setTint(binding.cardReport.inputLayoutPassword, false)
|
||||
}
|
||||
|
||||
private fun reportIssue() {
|
||||
if (binding.cardReport.optionUseAccount.isChecked) {
|
||||
if (!validateInput()) return
|
||||
val username = binding.cardReport.inputUsername.text.toString()
|
||||
val password = binding.cardReport.inputPassword.text.toString()
|
||||
sendBugReport(GithubLogin(username, password))
|
||||
} else {
|
||||
copyDeviceInfoToClipBoard()
|
||||
copyDeviceInfoToClipBoard()
|
||||
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = ISSUE_TRACKER_LINK.toUri()
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(i)
|
||||
}
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = ISSUE_TRACKER_LINK.toUri()
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
private fun copyDeviceInfoToClipBoard() {
|
||||
|
@ -166,67 +78,6 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
showToast(R.string.copied_device_info_to_clipboard)
|
||||
}
|
||||
|
||||
private fun validateInput(): Boolean {
|
||||
var hasErrors = false
|
||||
|
||||
if (binding.cardReport.optionUseAccount.isChecked) {
|
||||
if (binding.cardReport.inputUsername.text.isNullOrEmpty()) {
|
||||
setError(binding.cardReport.inputLayoutUsername, R.string.bug_report_no_username)
|
||||
hasErrors = true
|
||||
} else {
|
||||
removeError(binding.cardReport.inputLayoutUsername)
|
||||
}
|
||||
|
||||
if (binding.cardReport.inputPassword.text.isNullOrEmpty()) {
|
||||
setError(binding.cardReport.inputLayoutPassword, R.string.bug_report_no_password)
|
||||
hasErrors = true
|
||||
} else {
|
||||
removeError(binding.cardReport.inputLayoutPassword)
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.cardReport.inputTitle.text.isNullOrEmpty()) {
|
||||
setError(binding.cardReport.inputLayoutTitle, R.string.bug_report_no_title)
|
||||
hasErrors = true
|
||||
} else {
|
||||
removeError(binding.cardReport.inputLayoutTitle)
|
||||
}
|
||||
|
||||
if (binding.cardReport.inputDescription.text.isNullOrEmpty()) {
|
||||
setError(binding.cardReport.inputLayoutDescription, R.string.bug_report_no_description)
|
||||
hasErrors = true
|
||||
} else {
|
||||
removeError(binding.cardReport.inputLayoutDescription)
|
||||
}
|
||||
|
||||
return !hasErrors
|
||||
}
|
||||
|
||||
private fun setError(editTextLayout: TextInputLayout, @StringRes errorRes: Int) {
|
||||
editTextLayout.error = getString(errorRes)
|
||||
}
|
||||
|
||||
private fun removeError(editTextLayout: TextInputLayout) {
|
||||
editTextLayout.error = null
|
||||
}
|
||||
|
||||
private fun sendBugReport(login: GithubLogin) {
|
||||
if (!validateInput()) return
|
||||
|
||||
val bugTitle = binding.cardReport.inputTitle.text.toString()
|
||||
val bugDescription = binding.cardReport.inputDescription.text.toString()
|
||||
|
||||
val extraInfo = ExtraInfo()
|
||||
onSaveExtraInfo()
|
||||
|
||||
val report = Report(bugTitle, bugDescription, deviceInfo, extraInfo)
|
||||
val target = GithubTarget("RetroMusicPlayer", "RetroMusicPlayer")
|
||||
|
||||
reportIssue(report, target, login)
|
||||
}
|
||||
|
||||
private fun onSaveExtraInfo() {}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
|
@ -234,72 +85,6 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun reportIssue(
|
||||
report: Report,
|
||||
target: GithubTarget,
|
||||
login: GithubLogin
|
||||
) {
|
||||
val client: GitHubClient = if (login.shouldUseApiToken()) {
|
||||
GitHubClient().setOAuth2Token(login.apiToken)
|
||||
} else {
|
||||
GitHubClient().setCredentials(login.username, login.password)
|
||||
}
|
||||
|
||||
val issue = Issue().setTitle(report.title).setBody(report.getDescription())
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = try {
|
||||
IssueService(client).createIssue(target.username, target.repository, issue)
|
||||
RESULT_SUCCESS
|
||||
} catch (e: RequestException) {
|
||||
when (e.status) {
|
||||
STATUS_BAD_CREDENTIALS -> {
|
||||
if (login.shouldUseApiToken()) RESULT_INVALID_TOKEN else RESULT_BAD_CREDENTIALS
|
||||
}
|
||||
STATUS_ISSUES_NOT_ENABLED -> RESULT_ISSUES_NOT_ENABLED
|
||||
else -> {
|
||||
RESULT_UNKNOWN
|
||||
throw e
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
RESULT_UNKNOWN
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val activity = this@BugReportActivity
|
||||
when (result) {
|
||||
RESULT_SUCCESS -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_success)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
|
||||
.show()
|
||||
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_wrong_credentials)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_invalid_token)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_issues_not_available)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
else -> MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.bug_report_failed)
|
||||
.setMessage(R.string.bug_report_failed_unknown)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryToFinishActivity() {
|
||||
if (!isFinishing) {
|
||||
finish()
|
||||
|
@ -307,10 +92,7 @@ open class BugReportActivity : AbsThemeActivity() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val STATUS_BAD_CREDENTIALS = 401
|
||||
private const val STATUS_ISSUES_NOT_ENABLED = 410
|
||||
private const val ISSUE_TRACKER_LINK =
|
||||
"https://github.com/RetroMusicPlayer/RetroMusicPlayer"
|
||||
"https://github.com/MuntashirAkon/Metro/issues/new"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
package io.github.muntashirakon.music.activities.bugreport.model
|
||||
|
||||
import io.github.muntashirakon.music.activities.bugreport.model.github.ExtraInfo
|
||||
|
||||
class Report(
|
||||
val title: String,
|
||||
private val description: String,
|
||||
private val deviceInfo: DeviceInfo?,
|
||||
private val extraInfo: ExtraInfo
|
||||
) {
|
||||
fun getDescription(): String {
|
||||
return """
|
||||
$description
|
||||
|
||||
-
|
||||
|
||||
${deviceInfo?.toMarkdown()}
|
||||
|
||||
${extraInfo.toMarkdown()}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package io.github.muntashirakon.music.activities.bugreport.model.github
|
||||
|
||||
class ExtraInfo {
|
||||
private val extraInfo: MutableMap<String, String> = LinkedHashMap()
|
||||
fun put(key: String, value: String) {
|
||||
extraInfo[key] = value
|
||||
}
|
||||
|
||||
fun put(key: String, value: Boolean) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Double) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Float) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Long) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Int) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Any) {
|
||||
extraInfo[key] = value.toString()
|
||||
}
|
||||
|
||||
fun remove(key: String) {
|
||||
extraInfo.remove(key)
|
||||
}
|
||||
|
||||
fun toMarkdown(): String {
|
||||
if (extraInfo.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val output = StringBuilder()
|
||||
output.append(
|
||||
"""
|
||||
Extra info:
|
||||
---
|
||||
<table>
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
for (key in extraInfo.keys) {
|
||||
output
|
||||
.append("<tr><td>")
|
||||
.append(key)
|
||||
.append("</td><td>")
|
||||
.append(extraInfo[key])
|
||||
.append("</td></tr>\n")
|
||||
}
|
||||
output.append("</table>\n")
|
||||
return output.toString()
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package io.github.muntashirakon.music.activities.bugreport.model.github
|
||||
|
||||
import android.text.TextUtils
|
||||
|
||||
class GithubLogin {
|
||||
val apiToken: String?
|
||||
val password: String?
|
||||
val username: String?
|
||||
|
||||
constructor(username: String?, password: String?) {
|
||||
this.username = username
|
||||
this.password = password
|
||||
apiToken = null
|
||||
}
|
||||
|
||||
constructor(apiToken: String?) {
|
||||
username = null
|
||||
password = null
|
||||
this.apiToken = apiToken
|
||||
}
|
||||
|
||||
fun shouldUseApiToken(): Boolean {
|
||||
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password)
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package io.github.muntashirakon.music.activities.bugreport.model.github
|
||||
|
||||
class GithubTarget(val username: String, val repository: String)
|
|
@ -22,7 +22,6 @@ import android.view.*
|
|||
import androidx.activity.addCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
|
@ -59,13 +58,10 @@ import io.github.muntashirakon.music.interfaces.ICabCallback
|
|||
import io.github.muntashirakon.music.interfaces.ICabHolder
|
||||
import io.github.muntashirakon.music.model.Album
|
||||
import io.github.muntashirakon.music.model.Artist
|
||||
import io.github.muntashirakon.music.network.Result
|
||||
import io.github.muntashirakon.music.network.model.LastFmAlbum
|
||||
import io.github.muntashirakon.music.repository.RealRepository
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.RetroColorUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import com.afollestad.materialcab.attached.AttachedCab
|
||||
import com.afollestad.materialcab.attached.destroy
|
||||
import com.afollestad.materialcab.attached.isActive
|
||||
|
@ -162,14 +158,6 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
|
|||
)
|
||||
}
|
||||
|
||||
binding.fragmentAlbumContent.aboutAlbumText.setOnClickListener {
|
||||
if (binding.fragmentAlbumContent.aboutAlbumText.maxLines == 4) {
|
||||
binding.fragmentAlbumContent.aboutAlbumText.maxLines = Integer.MAX_VALUE
|
||||
} else {
|
||||
binding.fragmentAlbumContent.aboutAlbumText.maxLines = 4
|
||||
}
|
||||
}
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
|
||||
if (!handleBackPress()) {
|
||||
remove()
|
||||
|
@ -240,21 +228,6 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
|
|||
loadArtistImage(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner) { result ->
|
||||
when (result) {
|
||||
is Result.Loading -> {
|
||||
println("Loading")
|
||||
}
|
||||
is Result.Error -> {
|
||||
println("Error")
|
||||
}
|
||||
is Result.Success -> {
|
||||
aboutAlbum(result.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun moreAlbums(albums: List<Album>) {
|
||||
|
@ -274,41 +247,13 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
|
|||
binding.fragmentAlbumContent.moreRecyclerView.adapter = albumAdapter
|
||||
}
|
||||
|
||||
private fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
|
||||
if (lastFmAlbum.album != null) {
|
||||
if (lastFmAlbum.album.wiki != null) {
|
||||
binding.fragmentAlbumContent.aboutAlbumText.show()
|
||||
binding.fragmentAlbumContent.aboutAlbumTitle.show()
|
||||
binding.fragmentAlbumContent.aboutAlbumTitle.text =
|
||||
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
|
||||
binding.fragmentAlbumContent.aboutAlbumText.text =
|
||||
lastFmAlbum.album.wiki.content.parseAsHtml()
|
||||
}
|
||||
if (lastFmAlbum.album.listeners.isNotEmpty()) {
|
||||
binding.fragmentAlbumContent.listeners.show()
|
||||
binding.fragmentAlbumContent.listenersLabel.show()
|
||||
binding.fragmentAlbumContent.scrobbles.show()
|
||||
binding.fragmentAlbumContent.scrobblesLabel.show()
|
||||
|
||||
binding.fragmentAlbumContent.listeners.text =
|
||||
RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
|
||||
binding.fragmentAlbumContent.scrobbles.text =
|
||||
RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadArtistImage(artist: Artist) {
|
||||
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner) {
|
||||
moreAlbums(it)
|
||||
}
|
||||
GlideApp.with(requireContext())
|
||||
//.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
|
||||
.load(
|
||||
RetroGlideExtension.getArtistModel(
|
||||
artist,
|
||||
PreferenceUtil.isAllowedToDownloadMetadata(requireContext())
|
||||
)
|
||||
RetroGlideExtension.getArtistModel(artist)
|
||||
)
|
||||
.artistImageOptions(artist)
|
||||
.dontAnimate()
|
||||
|
|
|
@ -18,8 +18,6 @@ import androidx.lifecycle.*
|
|||
import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener
|
||||
import io.github.muntashirakon.music.model.Album
|
||||
import io.github.muntashirakon.music.model.Artist
|
||||
import io.github.muntashirakon.music.network.Result
|
||||
import io.github.muntashirakon.music.network.model.LastFmAlbum
|
||||
import io.github.muntashirakon.music.repository.RealRepository
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -52,11 +50,6 @@ class AlbumDetailsViewModel(
|
|||
emit(artist)
|
||||
}
|
||||
|
||||
fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData(IO) {
|
||||
emit(Result.Loading)
|
||||
emit(repository.albumInfo(album.artistName, album.title))
|
||||
}
|
||||
|
||||
fun getMoreAlbums(artist: Artist): LiveData<List<Album>> = liveData(IO) {
|
||||
artist.albums.filter { item -> item.id != albumId }.let { albums ->
|
||||
if (albums.isNotEmpty()) emit(albums)
|
||||
|
|
|
@ -13,9 +13,7 @@ import androidx.activity.addCallback
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.navigation.fragment.findNavController
|
||||
|
@ -39,8 +37,6 @@ import io.github.muntashirakon.music.interfaces.IAlbumClickListener
|
|||
import io.github.muntashirakon.music.interfaces.ICabCallback
|
||||
import io.github.muntashirakon.music.interfaces.ICabHolder
|
||||
import io.github.muntashirakon.music.model.Artist
|
||||
import io.github.muntashirakon.music.network.Result
|
||||
import io.github.muntashirakon.music.network.model.LastFmArtist
|
||||
import io.github.muntashirakon.music.repository.RealRepository
|
||||
import io.github.muntashirakon.music.util.*
|
||||
import com.afollestad.materialcab.attached.AttachedCab
|
||||
|
@ -67,8 +63,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
|
|||
private lateinit var songAdapter: SimpleSongAdapter
|
||||
private lateinit var albumAdapter: HorizontalAlbumAdapter
|
||||
private var forceDownload: Boolean = false
|
||||
private var lang: String? = null
|
||||
private var biography: Spanned? = null
|
||||
|
||||
private val savedSongSortOrder: String
|
||||
get() = PreferenceUtil.artistDetailSongSortOrder
|
||||
|
@ -105,14 +99,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
|
|||
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
|
||||
}
|
||||
|
||||
binding.fragmentArtistContent.biographyText.setOnClickListener {
|
||||
if (binding.fragmentArtistContent.biographyText.maxLines == 4) {
|
||||
binding.fragmentArtistContent.biographyText.maxLines = Integer.MAX_VALUE
|
||||
} else {
|
||||
binding.fragmentArtistContent.biographyText.maxLines = 4
|
||||
}
|
||||
}
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
|
||||
if (!handleBackPress()) {
|
||||
remove()
|
||||
|
@ -146,9 +132,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
|
|||
}
|
||||
this.artist = artist
|
||||
loadArtistImage(artist)
|
||||
if (PreferenceUtil.isAllowedToDownloadMetadata(requireContext())) {
|
||||
loadBiography(artist.name)
|
||||
}
|
||||
binding.artistTitle.text = artist.name
|
||||
binding.text.text = String.format(
|
||||
"%s • %s",
|
||||
|
@ -171,51 +154,6 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
|
|||
albumAdapter.swapDataSet(artist.albums)
|
||||
}
|
||||
|
||||
private fun loadBiography(
|
||||
name: String,
|
||||
lang: String? = Locale.getDefault().language,
|
||||
) {
|
||||
biography = null
|
||||
this.lang = lang
|
||||
detailsViewModel.getArtistInfo(name, lang, null)
|
||||
.observe(viewLifecycleOwner) { result ->
|
||||
when (result) {
|
||||
is Result.Loading -> println("Loading")
|
||||
is Result.Error -> println("Error")
|
||||
is Result.Success -> artistInfo(result.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun artistInfo(lastFmArtist: LastFmArtist?) {
|
||||
if (lastFmArtist != null && lastFmArtist.artist != null && lastFmArtist.artist.bio != null) {
|
||||
val bioContent = lastFmArtist.artist.bio.content
|
||||
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
|
||||
binding.fragmentArtistContent.run {
|
||||
biographyText.isVisible = true
|
||||
biographyTitle.isVisible = true
|
||||
biography = bioContent.parseAsHtml()
|
||||
biographyText.text = biography
|
||||
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
|
||||
listeners.show()
|
||||
listenersLabel.show()
|
||||
scrobbles.show()
|
||||
scrobblesLabel.show()
|
||||
listeners.text =
|
||||
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
|
||||
scrobbles.text =
|
||||
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the "lang" parameter is set and no biography is given, retry with default language
|
||||
if (biography == null && lang != null) {
|
||||
loadBiography(artist.name, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadArtistImage(artist: Artist) {
|
||||
GlideApp.with(requireContext()).asBitmapPalette().artistImageOptions(artist)
|
||||
.load(RetroGlideExtension.getArtistModel(artist))
|
||||
|
|
|
@ -17,8 +17,6 @@ package io.github.muntashirakon.music.fragments.artists
|
|||
import androidx.lifecycle.*
|
||||
import io.github.muntashirakon.music.interfaces.IMusicServiceEventListener
|
||||
import io.github.muntashirakon.music.model.Artist
|
||||
import io.github.muntashirakon.music.network.Result
|
||||
import io.github.muntashirakon.music.network.model.LastFmArtist
|
||||
import io.github.muntashirakon.music.repository.RealRepository
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -44,16 +42,6 @@ class ArtistDetailsViewModel(
|
|||
|
||||
fun getArtist(): LiveData<Artist> = artistDetails
|
||||
|
||||
fun getArtistInfo(
|
||||
name: String,
|
||||
lang: String?,
|
||||
cache: String?
|
||||
): LiveData<Result<LastFmArtist>> = liveData(IO) {
|
||||
emit(Result.Loading)
|
||||
val info = realRepository.artistInfo(name, lang, cache)
|
||||
emit(info)
|
||||
}
|
||||
|
||||
override fun onMediaStoreChanged() {
|
||||
fetchArtist()
|
||||
}
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
package io.github.muntashirakon.music.fragments.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.Preference
|
||||
import io.github.muntashirakon.music.AUTO_DOWNLOAD_IMAGES_POLICY
|
||||
import io.github.muntashirakon.music.R
|
||||
|
||||
/**
|
||||
|
@ -25,22 +22,9 @@ import io.github.muntashirakon.music.R
|
|||
*/
|
||||
|
||||
class ImageSettingFragment : AbsSettingsFragment() {
|
||||
override fun invalidateSettings() {
|
||||
val autoDownloadImagesPolicy: Preference = findPreference(AUTO_DOWNLOAD_IMAGES_POLICY)!!
|
||||
setSummary(autoDownloadImagesPolicy)
|
||||
autoDownloadImagesPolicy.setOnPreferenceChangeListener { _, o ->
|
||||
setSummary(autoDownloadImagesPolicy, o)
|
||||
true
|
||||
}
|
||||
}
|
||||
override fun invalidateSettings() {}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_images)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val preference: Preference? = findPreference(AUTO_DOWNLOAD_IMAGES_POLICY)
|
||||
preference?.let { setSummary(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
package io.github.muntashirakon.music.glide.artistimage
|
||||
|
||||
import android.content.Context
|
||||
import io.github.muntashirakon.music.model.Data
|
||||
import io.github.muntashirakon.music.model.DeezerResponse
|
||||
import io.github.muntashirakon.music.network.DeezerService
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
class ArtistImageFetcher(
|
||||
private val context: Context,
|
||||
private val deezerService: DeezerService,
|
||||
val model: ArtistImage,
|
||||
private val okhttp: OkHttpClient
|
||||
) : DataFetcher<InputStream> {
|
||||
|
||||
private var streamFetcher: OkHttpStreamFetcher? = null
|
||||
private var response: Call<DeezerResponse>? = null
|
||||
private var isCancelled: Boolean = false
|
||||
|
||||
override fun getDataClass(): Class<InputStream> {
|
||||
return InputStream::class.java
|
||||
}
|
||||
|
@ -40,52 +23,8 @@ class ArtistImageFetcher(
|
|||
}
|
||||
|
||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||
try {
|
||||
if (!MusicUtil.isArtistNameUnknown(model.artist.name) &&
|
||||
PreferenceUtil.isAllowedToDownloadMetadata(context)
|
||||
) {
|
||||
val artists = model.artist.name.split(",", "&")
|
||||
response = deezerService.getArtistImage(artists[0])
|
||||
response?.enqueue(object : Callback<DeezerResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<DeezerResponse>,
|
||||
response: Response<DeezerResponse>
|
||||
) {
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Request failed with code: " + response.code())
|
||||
}
|
||||
|
||||
if (isCancelled) {
|
||||
callback.onDataReady(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val deezerResponse = response.body()
|
||||
val imageUrl =
|
||||
deezerResponse?.data?.get(0)?.let { getHighestQuality(it) }
|
||||
// Fragile way to detect a place holder image returned from Deezer:
|
||||
// ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg"
|
||||
// the double slash implies no artist identified
|
||||
val placeHolder = imageUrl?.contains("/images/artist//") ?: false
|
||||
if (!placeHolder) {
|
||||
streamFetcher = OkHttpStreamFetcher(okhttp, GlideUrl(imageUrl))
|
||||
streamFetcher?.loadData(priority, callback)
|
||||
} else {
|
||||
callback.onDataReady(getFallbackAlbumImage())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
callback.onDataReady(getFallbackAlbumImage())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<DeezerResponse>, t: Throwable) {
|
||||
callback.onDataReady(getFallbackAlbumImage())
|
||||
}
|
||||
})
|
||||
} else callback.onDataReady(null)
|
||||
} catch (e: Exception) {
|
||||
callback.onLoadFailed(e)
|
||||
if (!MusicUtil.isArtistNameUnknown(model.artist.name)) {
|
||||
callback.onDataReady(getFallbackAlbumImage())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,24 +45,9 @@ class ArtistImageFetcher(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getHighestQuality(imageUrl: Data): String {
|
||||
return when {
|
||||
imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl
|
||||
imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig
|
||||
imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium
|
||||
imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall
|
||||
imageUrl.picture.isNotEmpty() -> imageUrl.picture
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun cleanup() {
|
||||
streamFetcher?.cleanup()
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
isCancelled = true
|
||||
response?.cancel()
|
||||
streamFetcher?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,23 +15,16 @@
|
|||
package io.github.muntashirakon.music.glide.artistimage
|
||||
|
||||
import android.content.Context
|
||||
import io.github.muntashirakon.music.network.DeezerService
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoader.LoadData
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ArtistImageLoader(
|
||||
val context: Context,
|
||||
private val deezerService: DeezerService,
|
||||
private val okhttp: OkHttpClient
|
||||
) : ModelLoader<ArtistImage, InputStream> {
|
||||
|
||||
override fun buildLoadData(
|
||||
|
@ -42,7 +35,7 @@ class ArtistImageLoader(
|
|||
): LoadData<InputStream> {
|
||||
return LoadData(
|
||||
ObjectKey(model.artist.name),
|
||||
ArtistImageFetcher(context, deezerService, model, okhttp)
|
||||
ArtistImageFetcher(context, model)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -55,44 +48,9 @@ class Factory(
|
|||
val context: Context
|
||||
) : ModelLoaderFactory<ArtistImage, InputStream> {
|
||||
|
||||
private var deezerService: DeezerService
|
||||
private var okHttp: OkHttpClient
|
||||
|
||||
init {
|
||||
okHttp =
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
deezerService = DeezerService.invoke(
|
||||
DeezerService.createDefaultOkHttpClient(context)
|
||||
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.addInterceptor(createLogInterceptor())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLogInterceptor(): Interceptor {
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
return interceptor
|
||||
}
|
||||
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ArtistImage, InputStream> {
|
||||
return ArtistImageLoader(
|
||||
context,
|
||||
deezerService,
|
||||
okHttp
|
||||
)
|
||||
return ArtistImageLoader(context)
|
||||
}
|
||||
|
||||
override fun teardown() {}
|
||||
|
||||
companion object {
|
||||
// we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
|
||||
private const val TIMEOUT: Long = 500
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class Contributor(
|
||||
@SerializedName("name") val name: String = "",
|
||||
@SerializedName("summary") val summary: String = "",
|
||||
@SerializedName("link") val link: String = "",
|
||||
@SerializedName("image") val image: String = ""
|
||||
) : Parcelable
|
|
@ -1,41 +0,0 @@
|
|||
package io.github.muntashirakon.music.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Data(
|
||||
@SerializedName("id")
|
||||
val id: String,
|
||||
@SerializedName("link")
|
||||
val link: String,
|
||||
@SerializedName("name")
|
||||
val name: String,
|
||||
@SerializedName("nb_album")
|
||||
val nbAlbum: Int,
|
||||
@SerializedName("nb_fan")
|
||||
val nbFan: Int,
|
||||
@SerializedName("picture")
|
||||
val picture: String,
|
||||
@SerializedName("picture_big")
|
||||
val pictureBig: String,
|
||||
@SerializedName("picture_medium")
|
||||
val pictureMedium: String,
|
||||
@SerializedName("picture_small")
|
||||
val pictureSmall: String,
|
||||
@SerializedName("picture_xl")
|
||||
val pictureXl: String,
|
||||
@SerializedName("radio")
|
||||
val radio: Boolean,
|
||||
@SerializedName("tracklist")
|
||||
val tracklist: String,
|
||||
@SerializedName("type")
|
||||
val type: String
|
||||
)
|
||||
|
||||
data class DeezerResponse(
|
||||
@SerializedName("data")
|
||||
val data: List<Data>,
|
||||
@SerializedName("next")
|
||||
val next: String,
|
||||
@SerializedName("total")
|
||||
val total: Int
|
||||
)
|
|
@ -1,69 +0,0 @@
|
|||
package io.github.muntashirakon.music.network
|
||||
|
||||
import android.content.Context
|
||||
import io.github.muntashirakon.music.model.DeezerResponse
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.create
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
private const val BASE_QUERY_ARTIST = "search/artist"
|
||||
private const val BASE_URL = "https://api.deezer.com/"
|
||||
|
||||
interface DeezerService {
|
||||
|
||||
@GET("$BASE_QUERY_ARTIST&limit=1")
|
||||
fun getArtistImage(
|
||||
@Query("q") artistName: String
|
||||
): Call<DeezerResponse>
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
client: okhttp3.Call.Factory
|
||||
): DeezerService {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.callFactory(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create()
|
||||
}
|
||||
|
||||
fun createDefaultOkHttpClient(
|
||||
context: Context
|
||||
): OkHttpClient.Builder = OkHttpClient.Builder()
|
||||
.cache(createDefaultCache(context))
|
||||
.addInterceptor(createCacheControlInterceptor())
|
||||
|
||||
private fun createDefaultCache(
|
||||
context: Context
|
||||
): Cache? {
|
||||
val cacheDir = File(context.applicationContext.cacheDir.absolutePath, "/okhttp-deezer/")
|
||||
if (cacheDir.mkdir() or cacheDir.isDirectory) {
|
||||
return Cache(cacheDir, 1024 * 1024 * 10)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createCacheControlInterceptor(): Interceptor {
|
||||
return Interceptor { chain ->
|
||||
val modifiedRequest = chain.request().newBuilder()
|
||||
.addHeader(
|
||||
"Cache-Control",
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
"max-age=31536000, max-stale=31536000"
|
||||
)
|
||||
).build()
|
||||
chain.proceed(modifiedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.network
|
||||
|
||||
import io.github.muntashirakon.music.network.model.LastFmAlbum
|
||||
import io.github.muntashirakon.music.network.model.LastFmArtist
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2019-11-26.
|
||||
*/
|
||||
|
||||
interface LastFMService {
|
||||
companion object {
|
||||
private const val API_KEY = "c679c8d3efa84613dc7dcb2e8d42da4c"
|
||||
const val BASE_QUERY_PARAMETERS = "?format=json&autocorrect=1&api_key=$API_KEY"
|
||||
}
|
||||
|
||||
@GET("$BASE_QUERY_PARAMETERS&method=artist.getinfo")
|
||||
suspend fun artistInfo(
|
||||
@Query("artist") artistName: String,
|
||||
@Query("lang") language: String?,
|
||||
@Header("Cache-Control") cacheControl: String?
|
||||
): LastFmArtist
|
||||
|
||||
@GET("$BASE_QUERY_PARAMETERS&method=album.getinfo")
|
||||
suspend fun albumInfo(
|
||||
@Query("artist") artistName: String,
|
||||
@Query("album") albumName: String
|
||||
): LastFmAlbum
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package io.github.muntashirakon.music.network
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface LyricsRestService {
|
||||
|
||||
@Headers("Cache-Control: public")
|
||||
@GET("/lyrics")
|
||||
suspend fun getLyrics(@Query("artist") artist: String, @Query("title") title: String): String
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Fatih Giris. All rights reserved.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.network
|
||||
|
||||
/**
|
||||
* Generic class that holds the network state
|
||||
*/
|
||||
sealed class Result<out R> {
|
||||
data class Success<out T>(val data: T) : Result<T>()
|
||||
object Loading : Result<Nothing>()
|
||||
data class Error(val error: Exception) : Result<Nothing>()
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package io.github.muntashirakon.music.network
|
||||
|
||||
import android.content.Context
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.BuildConfig
|
||||
import io.github.muntashirakon.music.network.conversion.LyricsConverterFactory
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
fun provideDefaultCache(): Cache? {
|
||||
val cacheDir = File(App.getContext().cacheDir.absolutePath, "/okhttp-lastfm/")
|
||||
if (cacheDir.mkdirs() || cacheDir.isDirectory) {
|
||||
return Cache(cacheDir, 1024 * 1024 * 10)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun logInterceptor(): Interceptor {
|
||||
val loggingInterceptor = HttpLoggingInterceptor()
|
||||
if (BuildConfig.DEBUG) {
|
||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
} else {
|
||||
// disable retrofit log on release
|
||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
|
||||
}
|
||||
return loggingInterceptor
|
||||
}
|
||||
|
||||
fun headerInterceptor(context: Context): Interceptor {
|
||||
return Interceptor {
|
||||
val original = it.request()
|
||||
val request = original.newBuilder()
|
||||
.header("User-Agent", context.packageName)
|
||||
.addHeader("Content-Type", "application/json; charset=utf-8")
|
||||
.method(original.method, original.body)
|
||||
.build()
|
||||
it.proceed(request)
|
||||
}
|
||||
}
|
||||
|
||||
fun provideOkHttp(context: Context, cache: Cache): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(logInterceptor())
|
||||
//.addInterceptor(headerInterceptor(context))
|
||||
.connectTimeout(1, TimeUnit.SECONDS)
|
||||
.readTimeout(1, TimeUnit.SECONDS)
|
||||
.cache(cache)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun provideLastFmRetrofit(client: OkHttpClient): Retrofit {
|
||||
val gson = GsonBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://ws.audioscrobbler.com/2.0/")
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.callFactory { request -> client.newCall(request) }
|
||||
.build()
|
||||
}
|
||||
|
||||
fun provideLastFmRest(retrofit: Retrofit): LastFMService {
|
||||
return retrofit.create(LastFMService::class.java)
|
||||
}
|
||||
|
||||
fun provideDeezerRest(retrofit: Retrofit): DeezerService {
|
||||
val newBuilder = retrofit.newBuilder()
|
||||
.baseUrl("https://api.deezer.com/")
|
||||
.build()
|
||||
return newBuilder.create(DeezerService::class.java)
|
||||
}
|
||||
|
||||
fun provideLyrics(retrofit: Retrofit): LyricsRestService {
|
||||
val newBuilder = retrofit.newBuilder()
|
||||
.baseUrl("https://makeitpersonal.co")
|
||||
.addConverterFactory(LyricsConverterFactory())
|
||||
.build()
|
||||
return newBuilder.create(LyricsRestService::class.java)
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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 io.github.muntashirakon.music.network.conversion
|
||||
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Converter
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class LyricsConverterFactory : Converter.Factory() {
|
||||
|
||||
override fun responseBodyConverter(
|
||||
type: Type,
|
||||
annotations: Array<out Annotation>,
|
||||
retrofit: Retrofit
|
||||
): Converter<ResponseBody, *>? {
|
||||
return if (String::class.java == type) {
|
||||
Converter<ResponseBody, String> { value -> value.string() }
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun requestBodyConverter(
|
||||
type: Type,
|
||||
parameterAnnotations: Array<Annotation>,
|
||||
methodAnnotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): Converter<*, RequestBody>? {
|
||||
|
||||
return if (String::class.java == type) {
|
||||
Converter<String, RequestBody> { value -> value.toRequestBody(MEDIA_TYPE) }
|
||||
} else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val MEDIA_TYPE = "text/plain".toMediaTypeOrNull()
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.network.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LastFmAlbum {
|
||||
|
||||
@Expose private Album album;
|
||||
|
||||
public Album getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public void setAlbum(Album album) {
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
public static class Album {
|
||||
|
||||
@Expose public String listeners;
|
||||
@Expose public String playcount;
|
||||
@Expose private List<Image> image = new ArrayList<>();
|
||||
@Expose private String name;
|
||||
@Expose private Tags tags;
|
||||
@Expose private Wiki wiki;
|
||||
|
||||
public List<Image> getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(List<Image> image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public String getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void setListeners(final String listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPlaycount() {
|
||||
return playcount;
|
||||
}
|
||||
|
||||
public void setPlaycount(final String playcount) {
|
||||
this.playcount = playcount;
|
||||
}
|
||||
|
||||
public Tags getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public Wiki getWiki() {
|
||||
return wiki;
|
||||
}
|
||||
|
||||
public void setWiki(Wiki wiki) {
|
||||
this.wiki = wiki;
|
||||
}
|
||||
|
||||
public static class Image {
|
||||
|
||||
@SerializedName("#text")
|
||||
@Expose
|
||||
private String Text;
|
||||
|
||||
@Expose private String size;
|
||||
|
||||
public String getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(String size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return Text;
|
||||
}
|
||||
|
||||
public void setText(String Text) {
|
||||
this.Text = Text;
|
||||
}
|
||||
}
|
||||
|
||||
public class Tags {
|
||||
|
||||
@Expose
|
||||
private final List<Tag> tag = null;
|
||||
|
||||
public List<Tag> getTag() {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
public class Tag {
|
||||
|
||||
@Expose private String name;
|
||||
|
||||
@Expose private String url;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
public class Wiki {
|
||||
|
||||
@Expose private String content;
|
||||
|
||||
@Expose private String published;
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public void setPublished(final String published) {
|
||||
this.published = published;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.network.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LastFmArtist {
|
||||
|
||||
@Expose private Artist artist;
|
||||
|
||||
public Artist getArtist() {
|
||||
return artist;
|
||||
}
|
||||
|
||||
public void setArtist(Artist artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public static class Artist {
|
||||
|
||||
@Expose public Stats stats;
|
||||
@Expose private Bio bio;
|
||||
@Expose private List<Image> image = new ArrayList<>();
|
||||
|
||||
public Bio getBio() {
|
||||
return bio;
|
||||
}
|
||||
|
||||
public void setBio(Bio bio) {
|
||||
this.bio = bio;
|
||||
}
|
||||
|
||||
public List<Image> getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(List<Image> image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public static class Image {
|
||||
|
||||
@SerializedName("#text")
|
||||
@Expose
|
||||
private String Text;
|
||||
|
||||
@Expose private String size;
|
||||
|
||||
public String getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(String size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return Text;
|
||||
}
|
||||
|
||||
public void setText(String Text) {
|
||||
this.Text = Text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Stats {
|
||||
|
||||
@Expose public String listeners;
|
||||
|
||||
@Expose public String playcount;
|
||||
|
||||
public String getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public void setListeners(final String listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
public String getPlaycount() {
|
||||
return playcount;
|
||||
}
|
||||
|
||||
public void setPlaycount(final String playcount) {
|
||||
this.playcount = playcount;
|
||||
}
|
||||
}
|
||||
|
||||
public class Bio {
|
||||
|
||||
@Expose private String content;
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.network.model;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Created by hemanths on 15/06/17. */
|
||||
public class LastFmTrack {
|
||||
|
||||
@Expose private Track track;
|
||||
|
||||
public Track getTrack() {
|
||||
return track;
|
||||
}
|
||||
|
||||
public void setTrack(Track track) {
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
public static class Track {
|
||||
@SerializedName("name")
|
||||
@Expose
|
||||
private String name;
|
||||
|
||||
@Expose private Album album;
|
||||
@Expose private Wiki wiki;
|
||||
@Expose private Toptags toptags;
|
||||
@Expose private Artist artist;
|
||||
|
||||
public Album getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public Wiki getWiki() {
|
||||
return wiki;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Toptags getToptags() {
|
||||
return toptags;
|
||||
}
|
||||
|
||||
public static class Artist {
|
||||
|
||||
@Expose private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Wiki {
|
||||
@Expose private String published;
|
||||
|
||||
public String getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public void setPublished(String published) {
|
||||
this.published = published;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Toptags {
|
||||
@Expose
|
||||
private final List<Tag> tag = null;
|
||||
|
||||
public List<Tag> getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static class Tag {
|
||||
@Expose private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Album {
|
||||
@Expose private String artist;
|
||||
@Expose private List<Image> image = null;
|
||||
@Expose private String title;
|
||||
|
||||
@SerializedName("@attr")
|
||||
@Expose
|
||||
private Attr attr;
|
||||
|
||||
public Attr getAttr() {
|
||||
return attr;
|
||||
}
|
||||
|
||||
public void setAttr(Attr attr) {
|
||||
this.attr = attr;
|
||||
}
|
||||
|
||||
public String getArtist() {
|
||||
return artist;
|
||||
}
|
||||
|
||||
public void setArtist(String artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public List<Image> getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(List<Image> image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public static class Attr {
|
||||
@Expose private String position;
|
||||
|
||||
public String getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(String position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public class Image {
|
||||
|
||||
@SerializedName("#text")
|
||||
@Expose
|
||||
private String text;
|
||||
|
||||
@Expose private String size;
|
||||
|
||||
public String getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,24 +22,9 @@ import io.github.muntashirakon.music.db.*
|
|||
import io.github.muntashirakon.music.fragments.search.Filter
|
||||
import io.github.muntashirakon.music.model.*
|
||||
import io.github.muntashirakon.music.model.smartplaylist.NotPlayedPlaylist
|
||||
import io.github.muntashirakon.music.network.LastFMService
|
||||
import io.github.muntashirakon.music.network.Result
|
||||
import io.github.muntashirakon.music.network.Result.*
|
||||
import io.github.muntashirakon.music.network.model.LastFmAlbum
|
||||
import io.github.muntashirakon.music.network.model.LastFmArtist
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
interface Repository {
|
||||
|
||||
fun songsFlow(): Flow<Result<List<Song>>>
|
||||
fun albumsFlow(): Flow<Result<List<Album>>>
|
||||
fun artistsFlow(): Flow<Result<List<Artist>>>
|
||||
fun playlistsFlow(): Flow<Result<List<Playlist>>>
|
||||
fun genresFlow(): Flow<Result<List<Genre>>>
|
||||
fun historySong(): List<HistoryEntity>
|
||||
fun favorites(): LiveData<List<SongEntity>>
|
||||
fun observableHistorySongs(): LiveData<List<Song>>
|
||||
|
@ -55,8 +40,6 @@ interface Repository {
|
|||
suspend fun search(query: String?, filter: Filter): MutableList<Any>
|
||||
suspend fun getPlaylistSongs(playlist: Playlist): List<Song>
|
||||
suspend fun getGenre(genreId: Long): List<Song>
|
||||
suspend fun artistInfo(name: String, lang: String?, cache: String?): Result<LastFmArtist>
|
||||
suspend fun albumInfo(artist: String, album: String): Result<LastFmAlbum>
|
||||
suspend fun artistById(artistId: Long): Artist
|
||||
suspend fun albumArtistByName(name: String): Artist
|
||||
suspend fun recentArtists(): List<Artist>
|
||||
|
@ -74,8 +57,6 @@ interface Repository {
|
|||
suspend fun playlists(): Home
|
||||
suspend fun homeSections(): List<Home>
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
|
||||
suspend fun playlist(playlistId: Long): Playlist
|
||||
suspend fun fetchPlaylistWithSongs(): List<PlaylistWithSongs>
|
||||
suspend fun playlistSongs(playlistWithSongs: PlaylistWithSongs): List<Song>
|
||||
|
@ -115,7 +96,6 @@ interface Repository {
|
|||
|
||||
class RealRepository(
|
||||
private val context: Context,
|
||||
private val lastFMService: LastFMService,
|
||||
private val songRepository: SongRepository,
|
||||
private val albumRepository: AlbumRepository,
|
||||
private val artistRepository: ArtistRepository,
|
||||
|
@ -183,59 +163,6 @@ class RealRepository(
|
|||
|
||||
override suspend fun getGenre(genreId: Long): List<Song> = genreRepository.songs(genreId)
|
||||
|
||||
override suspend fun artistInfo(
|
||||
name: String,
|
||||
lang: String?,
|
||||
cache: String?
|
||||
): Result<LastFmArtist> {
|
||||
return try {
|
||||
Success(lastFMService.artistInfo(name, lang, cache))
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun albumInfo(
|
||||
artist: String,
|
||||
album: String
|
||||
): Result<LastFmAlbum> {
|
||||
return try {
|
||||
val lastFmAlbum = lastFMService.albumInfo(artist, album)
|
||||
Success(lastFmAlbum)
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override suspend fun homeSectionsFlow(): Flow<Result<List<Home>>> {
|
||||
val homes = MutableStateFlow<Result<List<Home>>>(value = Loading)
|
||||
val homeSections = mutableListOf<Home>()
|
||||
val sections = listOf(
|
||||
topArtistsHome(),
|
||||
topAlbumsHome(),
|
||||
recentArtistsHome(),
|
||||
recentAlbumsHome(),
|
||||
suggestionsHome(),
|
||||
favoritePlaylistHome(),
|
||||
genresHome()
|
||||
)
|
||||
for (section in sections) {
|
||||
if (section.arrayList.isNotEmpty()) {
|
||||
println("${section.homeSection} -> ${section.arrayList.size}")
|
||||
homeSections.add(section)
|
||||
}
|
||||
}
|
||||
if (homeSections.isEmpty()) {
|
||||
homes.value = Error(Exception(Throwable("No items")))
|
||||
} else {
|
||||
homes.value = Success(homeSections)
|
||||
}
|
||||
return homes
|
||||
}
|
||||
|
||||
override suspend fun homeSections(): List<Home> {
|
||||
val homeSections = mutableListOf<Home>()
|
||||
val sections: List<Home> = listOf(
|
||||
|
@ -420,53 +347,4 @@ class RealRepository(
|
|||
return Home(songs, FAVOURITES, R.string.favorites)
|
||||
}
|
||||
|
||||
override fun songsFlow(): Flow<Result<List<Song>>> = flow {
|
||||
emit(Loading)
|
||||
val data = songRepository.songs()
|
||||
if (data.isEmpty()) {
|
||||
emit(Error(Exception(Throwable("No items"))))
|
||||
} else {
|
||||
emit(Success(data))
|
||||
}
|
||||
}
|
||||
|
||||
override fun albumsFlow(): Flow<Result<List<Album>>> = flow {
|
||||
emit(Loading)
|
||||
val data = albumRepository.albums()
|
||||
if (data.isEmpty()) {
|
||||
emit(Error(Exception(Throwable("No items"))))
|
||||
} else {
|
||||
emit(Success(data))
|
||||
}
|
||||
}
|
||||
|
||||
override fun artistsFlow(): Flow<Result<List<Artist>>> = flow {
|
||||
emit(Loading)
|
||||
val data = artistRepository.artists()
|
||||
if (data.isEmpty()) {
|
||||
emit(Error(Exception(Throwable("No items"))))
|
||||
} else {
|
||||
emit(Success(data))
|
||||
}
|
||||
}
|
||||
|
||||
override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow {
|
||||
emit(Loading)
|
||||
val data = playlistRepository.playlists()
|
||||
if (data.isEmpty()) {
|
||||
emit(Error(Exception(Throwable("No items"))))
|
||||
} else {
|
||||
emit(Success(data))
|
||||
}
|
||||
}
|
||||
|
||||
override fun genresFlow(): Flow<Result<List<Genre>>> = flow {
|
||||
emit(Loading)
|
||||
val data = genreRepository.genres()
|
||||
if (data.isEmpty()) {
|
||||
emit(Error(Exception(Throwable("No items"))))
|
||||
} else {
|
||||
emit(Success(data))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
package io.github.muntashirakon.music.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.res.use
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
|
@ -115,12 +111,6 @@ object PreferenceUtil {
|
|||
putString(SAF_SDCARD_URI, value)
|
||||
}
|
||||
|
||||
val autoDownloadImagesPolicy
|
||||
get() = sharedPreferences.getStringOrDefault(
|
||||
AUTO_DOWNLOAD_IMAGES_POLICY,
|
||||
"only_wifi"
|
||||
)
|
||||
|
||||
var albumArtistsOnly
|
||||
get() = sharedPreferences.getBoolean(
|
||||
ALBUM_ARTISTS_ONLY,
|
||||
|
@ -336,26 +326,6 @@ object PreferenceUtil {
|
|||
|
||||
val isLockScreen get() = sharedPreferences.getBoolean(LOCK_SCREEN, false)
|
||||
|
||||
fun isAllowedToDownloadMetadata(context: Context): Boolean {
|
||||
return when (autoDownloadImagesPolicy) {
|
||||
"always" -> true
|
||||
"only_wifi" -> {
|
||||
val connectivityManager = context.getSystemService<ConnectivityManager>()
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
val network = connectivityManager?.activeNetwork
|
||||
val capabilities = connectivityManager?.getNetworkCapabilities(network)
|
||||
capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
} else {
|
||||
val netInfo = connectivityManager?.activeNetworkInfo
|
||||
netInfo != null && netInfo.type == ConnectivityManager.TYPE_WIFI && netInfo.isConnectedOrConnecting
|
||||
}
|
||||
}
|
||||
"never" -> false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var lyricsOption
|
||||
get() = sharedPreferences.getInt(LYRICS_OPTIONS, 1)
|
||||
set(value) = sharedPreferences.edit {
|
||||
|
|
|
@ -33,13 +33,6 @@
|
|||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/card_report"
|
||||
layout="@layout/bug_report_card_report"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<include
|
||||
android:id="@+id/card_device_info"
|
||||
layout="@layout/bug_report_card_device_info"
|
||||
|
@ -60,5 +53,5 @@
|
|||
style="@style/Fab"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
app:srcCompat="@drawable/ic_send" />
|
||||
app:srcCompat="@drawable/ic_open_in_browser" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,185 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/optionUseAccount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="56dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="8dp"
|
||||
android:text="@string/bug_report_use_account"
|
||||
android:textAppearance="@style/TextViewSubtitle1" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="8dp"
|
||||
android:text="@string/your_account_data_is_only_used_for_authentication"
|
||||
android:textAppearance="@style/TextViewCaption" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="72dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/bug_report_issue"
|
||||
android:textAppearance="@style/TextViewOverline"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/inputLayoutTitle"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/inputTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/title"
|
||||
android:inputType="textCapSentences"
|
||||
android:singleLine="true" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/inputLayoutDescription"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/inputDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:hint="@string/description"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="3" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/login"
|
||||
android:textAppearance="@style/TextViewOverline"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/inputLayoutUsername"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/inputUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/username"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:singleLine="true" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/inputLayoutPassword"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/inputPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/password"
|
||||
android:imeOptions="actionSend"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<com.google.android.material.radiobutton.MaterialRadioButton
|
||||
android:id="@+id/optionAnonymous"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="56dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="8dp"
|
||||
android:text="@string/bug_report_manual"
|
||||
android:textAppearance="@style/TextViewSubtitle1" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="8dp"
|
||||
android:text="@string/you_will_be_forwarded_to_the_issue_tracker_website"
|
||||
android:textAppearance="@style/TextViewCaption" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
|
@ -97,106 +97,10 @@
|
|||
tools:listitem="@layout/item_album_card"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/aboutAlbumTitle"
|
||||
style="@style/SubTitleTextAppearance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:text="@string/songs"
|
||||
android:textAppearance="@style/TextViewOverline"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/moreRecyclerView"
|
||||
tools:text="About Album"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/aboutAlbumText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:maxLines="4"
|
||||
android:padding="16dp"
|
||||
android:textSize="14sp"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/aboutAlbumTitle"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/listenersLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/listeners_label"
|
||||
android:textAppearance="@style/TextViewSubtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/aboutAlbumText"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/scrobblesLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/scrobbles_label"
|
||||
android:textAppearance="@style/TextViewSubtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/listenersLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/aboutAlbumText"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/listeners"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="@id/listenersLabel"
|
||||
app:layout_constraintStart_toStartOf="@id/listenersLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/listenersLabel"
|
||||
tools:text="100000"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/scrobbles"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextViewBody1"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="@id/scrobblesLabel"
|
||||
app:layout_constraintStart_toStartOf="@id/scrobblesLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/scrobblesLabel"
|
||||
tools:text="100000"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/listeners" />
|
||||
app:layout_constraintTop_toBottomOf="@id/moreRecyclerView" />
|
||||
</io.github.muntashirakon.music.views.insets.InsetsConstraintLayout>
|
|
@ -107,106 +107,9 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/songTitle"
|
||||
tools:listitem="@layout/item_song" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/biographyTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="@string/biography"
|
||||
android:textAppearance="@style/TextViewOverline"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/recyclerView"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/biographyText"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:maxLines="4"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/biographyTitle"
|
||||
tools:text="@string/bug_report_summary"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/listenersLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/listeners_label"
|
||||
android:textAppearance="@style/TextViewSubtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/biographyText"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/scrobblesLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/scrobbles_label"
|
||||
android:textAppearance="@style/TextViewSubtitle1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/listenersLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/biographyText"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/listeners"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextViewBody2"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="@id/listenersLabel"
|
||||
app:layout_constraintStart_toStartOf="@id/listenersLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/listenersLabel"
|
||||
tools:text="100000"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/scrobbles"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextViewBody2"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="@id/scrobblesLabel"
|
||||
app:layout_constraintStart_toStartOf="@id/scrobblesLabel"
|
||||
app:layout_constraintTop_toBottomOf="@id/scrobblesLabel"
|
||||
tools:text="100000"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/listeners" />
|
||||
app:layout_constraintTop_toBottomOf="@id/recyclerView" />
|
||||
</io.github.muntashirakon.music.views.insets.InsetsConstraintLayout>
|
|
@ -10,11 +10,4 @@
|
|||
android:title="@string/pref_title_ignore_media_store_artwork"
|
||||
app:icon="@drawable/ic_image" />
|
||||
|
||||
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/pref_auto_download_images_titles"
|
||||
android:entryValues="@array/pref_auto_download_images_values"
|
||||
android:key="auto_download_images_policy"
|
||||
android:layout="@layout/list_item_view"
|
||||
android:title="@string/pref_title_auto_download_artist_images" />
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Add table
Add a link
Reference in a new issue