Skip to content

Commit

Permalink
Release 0.3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
z-huang committed Aug 14, 2022
2 parents 0fcdcaf + 3021f71 commit d690bfa
Show file tree
Hide file tree
Showing 48 changed files with 552 additions and 138 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ No ads, free, and simple.
> **Note 2:** The name of this app is temporary. It will be changed in the future.
> **Note 3:** We are currently making a change about the YouTube library. The development is in `feature/innertube` branch. Issues are put on hold until `feature/innertube` gets merged into `dev` branch.
## Description

With this app, you're like getting a free music streaming service. You can listen to music from YouTube/YouTube Music and build your own library. What's more, songs can be downloaded for offline playback. The metadata of songs and artists are fully editable. You can also create playlists to organize your songs. The aim of _Music_ is to enable everyone to listen to music at no cost by an easy-to-use, practical and ad-free application.
Expand Down Expand Up @@ -59,12 +60,6 @@ The ability to retrieve information and stream data from YouTube/YouTube Music i
<img src="https://raw.githubusercontent.com/z-huang/music/dev/screenshots/settings.jpg" width="170" />
</p>

## Roadmap

The overall plan for this project at current stage:
1. Improve user interface
2. Modify [NewPipe Extractor](https://github.com/TeamNewPipe/NewPipeExtractor) to support more album and artist information and audio normalization

## Installation

You can install _Music_ using the following methods:
Expand Down
34 changes: 17 additions & 17 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ android {
applicationId = "com.zionhuang.music"
minSdk = 26
targetSdk = 31
versionCode = 8
versionName = "0.3.1"
versionCode = 9
versionName = "0.3.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
applicationVariants.all {
Expand Down Expand Up @@ -101,14 +101,14 @@ materialThemeBuilder {
dependencies {
implementation(fileTree("dir" to "libs", "include" to "*.jar"))
// Kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
// AndroidX
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.appcompat:appcompat:1.4.2")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.4.1")
implementation("androidx.preference:preference-ktx:1.2.0")
implementation("androidx.vectordrawable:vectordrawable:1.1.0")
Expand All @@ -124,12 +124,12 @@ dependencies {
implementation("androidx.work:work-runtime-ktx:2.7.1")
implementation("androidx.recyclerview:recyclerview-selection:1.1.0")
implementation("androidx.transition:transition-ktx:1.4.1")
implementation("com.google.android.material:material:1.6.0")
implementation("com.google.android.material:material:1.6.1")
// Gson
implementation("com.google.code.gson:gson:2.9.0")
// ExoPlayer
implementation("com.google.android.exoplayer:exoplayer:2.16.1")
implementation("com.google.android.exoplayer:extension-mediasession:2.16.1")
implementation("com.google.android.exoplayer:exoplayer:2.17.1")
implementation("com.google.android.exoplayer:extension-mediasession:2.17.1")
// Paging
implementation("androidx.paging:paging-runtime-ktx:3.1.1")
testImplementation("androidx.paging:paging-common-ktx:3.1.1")
Expand All @@ -143,17 +143,17 @@ dependencies {
testImplementation("androidx.room:room-testing:2.4.2")
// NewPipe Extractor
implementation("com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751")
implementation("com.github.TeamNewPipe:NewPipeExtractor:v$newpipeVersion")
implementation("com.github.TeamNewPipe:NewPipeExtractor:76aad92fa54524f20c3338ab568c9cd6b50c9d33")
// Apache Utils
implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("org.apache.commons:commons-text:1.9")
// OkHttp
implementation("com.squareup.okhttp3:okhttp:4.9.2")
implementation("com.squareup.okhttp3:okhttp:4.9.3")
// Glide
implementation("com.github.bumptech.glide:glide:4.12.0")
implementation("com.github.bumptech.glide:annotations:4.12.0")
implementation("com.github.bumptech.glide:okhttp3-integration:4.12.0")
kapt("com.github.bumptech.glide:compiler:4.12.0")
implementation("com.github.bumptech.glide:glide:4.13.2")
implementation("com.github.bumptech.glide:annotations:4.13.2")
implementation("com.github.bumptech.glide:okhttp3-integration:4.13.1")
kapt("com.github.bumptech.glide:compiler:4.13.1")
// Jsoup
implementation("org.jsoup:jsoup:1.14.3")
// Fast Scroll
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.zionhuang.music.extensions

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.SharedPreferences
Expand All @@ -9,6 +8,7 @@ import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.use
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
Expand All @@ -19,8 +19,8 @@ import com.zionhuang.music.utils.preference.serializablePreference

fun Context.getDensity(): Float = resources.displayMetrics.density

tailrec fun Context?.getActivity(): Activity? = when (this) {
is Activity -> this
tailrec fun Context?.getActivity(): AppCompatActivity? = when (this) {
is AppCompatActivity -> this
else -> (this as? ContextWrapper)?.baseContext?.getActivity()
}

Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/com/zionhuang/music/extensions/FragmentExt.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package com.zionhuang.music.extensions

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment

fun Fragment.requireAppCompatActivity(): AppCompatActivity = requireActivity() as AppCompatActivity
fun Fragment.requireAppCompatActivity(): AppCompatActivity = requireActivity() as AppCompatActivity

fun DialogFragment.show(context: Context, tag: String? = null) {
context.getActivity()?.let {
show(it.supportFragmentManager, tag)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ object SongRepository : LocalRepository {
// TODO Exception handling
val stream = StreamHelper.getHighestQualityAudioStream(streamInfo.audioStreams)!!
val downloadManager = context.getSystemService<DownloadManager>()!!
val req = DownloadManager.Request(stream.url.toUri())
val req = DownloadManager.Request(stream.content.toUri())
.setTitle(song.title)
.setDestinationUri(getSongFile(id).toUri())
.setVisibleInDownloadsUi(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI.onNavDestinationSelected
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.*
Expand Down Expand Up @@ -107,6 +108,11 @@ class MainActivity : ThemedBindingActivity<ActivityMainBinding>(), NavController
))
binding.toolbar.setupWithNavController(navController, appBarConfiguration)
binding.bottomNav.setupWithNavController(navController)
binding.bottomNav.setOnItemSelectedListener { item ->
onNavDestinationSelected(item, navController)
item.isChecked = true
true
}
navController.addOnDestinationChangedListener(this)

replaceFragment(R.id.bottom_controls_container, BottomControlsFragment())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.zionhuang.music.ui.fragments

import android.os.Bundle
import android.view.*
import androidx.annotation.MenuRes
import androidx.core.os.bundleOf
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.navigation.NavigationView
import com.zionhuang.music.R
import java.io.Serializable

typealias MenuModifier = Menu.() -> Unit
typealias MenuItemClickListener = (MenuItem) -> Unit

class MenuBottomSheetDialogFragment : BottomSheetDialogFragment() {
@MenuRes
private var menuResId: Int = 0
private var menuModifier: MenuModifier? = null
private var onMenuItemClicked: MenuItemClickListener? = null

@Suppress("UNCHECKED_CAST")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
menuResId = requireArguments().getInt(KEY_MENU_RES_ID, 0)
menuModifier = requireArguments().getSerializable(KEY_MENU_MODIFIER) as? MenuModifier
onMenuItemClicked = requireArguments().getSerializable(KEY_MENU_LISTENER) as? MenuItemClickListener
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.menu_bottom_sheet_dialog, container, false)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<NavigationView>(R.id.navigation_view).apply {
inflateMenu(menuResId)
menuModifier?.invoke(menu)
setNavigationItemSelectedListener {
onMenuItemClicked?.invoke(it)
dismiss()
true
}
}
}

fun setMenuModifier(menuModifier: MenuModifier): MenuBottomSheetDialogFragment {
requireArguments().putSerializable(KEY_MENU_MODIFIER, menuModifier as Serializable)
return this
}

fun setOnMenuItemClickListener(listener: MenuItemClickListener): MenuBottomSheetDialogFragment {
requireArguments().putSerializable(KEY_MENU_LISTENER, listener as Serializable)
return this
}

companion object {
private const val KEY_MENU_RES_ID = "MENU_RES_ID"
private const val KEY_MENU_MODIFIER = "MENU_MODIFIER"
private const val KEY_MENU_LISTENER = "LISTENER"

fun newInstance(@MenuRes menuResId: Int) = MenuBottomSheetDialogFragment().apply {
arguments = bundleOf(KEY_MENU_RES_ID to menuResId)
}

}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
package com.zionhuang.music.ui.viewholders

import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import com.zionhuang.music.R
import com.zionhuang.music.databinding.ItemArtistBinding
import com.zionhuang.music.db.entities.ArtistEntity
import com.zionhuang.music.extensions.context
import com.zionhuang.music.extensions.show
import com.zionhuang.music.ui.fragments.MenuBottomSheetDialogFragment
import com.zionhuang.music.ui.listeners.ArtistPopupMenuListener

class ArtistViewHolder(
val binding: ItemArtistBinding,
private val popupMenuListener: ArtistPopupMenuListener?,
val binding: ItemArtistBinding,
private val popupMenuListener: ArtistPopupMenuListener?,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(artist: ArtistEntity) {
binding.artist = artist
binding.btnMoreAction.setOnClickListener { view ->
PopupMenu(view.context, view).apply {
inflate(R.menu.artist)
setOnMenuItemClickListener {
binding.btnMoreAction.setOnClickListener {
MenuBottomSheetDialogFragment
.newInstance(R.menu.artist)
.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_edit -> popupMenuListener?.editArtist(artist, binding.context)
R.id.action_delete -> popupMenuListener?.deleteArtist(artist)
}
true
}
show()
}
.show(binding.context)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.zionhuang.music.ui.viewholders

import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import com.zionhuang.music.R
import com.zionhuang.music.databinding.ItemPlaylistBinding
import com.zionhuang.music.db.entities.PlaylistEntity
import com.zionhuang.music.extensions.context
import com.zionhuang.music.extensions.show
import com.zionhuang.music.ui.fragments.MenuBottomSheetDialogFragment
import com.zionhuang.music.ui.listeners.PlaylistPopupMenuListener

class PlaylistViewHolder(
Expand All @@ -14,18 +15,16 @@ class PlaylistViewHolder(
) : RecyclerView.ViewHolder(binding.root) {
fun bind(playlist: PlaylistEntity) {
binding.playlist = playlist
binding.btnMoreAction.setOnClickListener { view ->
PopupMenu(view.context, view).apply {
inflate(R.menu.artist)
setOnMenuItemClickListener {
binding.btnMoreAction.setOnClickListener {
MenuBottomSheetDialogFragment
.newInstance(R.menu.artist)
.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_edit -> popupMenuListener?.editPlaylist(playlist, binding.context)
R.id.action_delete -> popupMenuListener?.deletePlaylist(playlist)
}
true
}
show()
}
.show(binding.context)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.zionhuang.music.ui.viewholders

import android.widget.PopupMenu
import androidx.core.view.isVisible
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.zionhuang.music.R
import com.zionhuang.music.databinding.ItemSearchStreamBinding
import com.zionhuang.music.extensions.context
import com.zionhuang.music.extensions.id
import com.zionhuang.music.extensions.load
import com.zionhuang.music.extensions.roundCorner
import com.zionhuang.music.extensions.*
import com.zionhuang.music.repos.SongRepository
import com.zionhuang.music.ui.fragments.MenuBottomSheetDialogFragment
import com.zionhuang.music.ui.listeners.StreamPopupMenuListener
import com.zionhuang.music.ui.viewholders.base.SearchViewHolder
import com.zionhuang.music.utils.makeTimeString
Expand Down Expand Up @@ -46,21 +43,19 @@ class SearchStreamViewHolder(
}

private fun setupMenu(item: StreamInfoItem) {
binding.btnMoreAction.setOnClickListener { view ->
PopupMenu(view.context, view).apply {
inflate(R.menu.search_item)
setOnMenuItemClickListener {
binding.btnMoreAction.setOnClickListener {
MenuBottomSheetDialogFragment
.newInstance(R.menu.stream)
.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_add_to_library -> listener?.addToLibrary(item)
R.id.action_play_next -> listener?.playNext(item)
R.id.action_add_to_queue -> listener?.addToQueue(item)
R.id.action_add_to_playlist -> listener?.addToPlaylist(item, binding.context)
R.id.action_download -> listener?.download(item, binding.context)
}
true
}
show()
}
.show(binding.context)
}
}

Expand Down
Loading

0 comments on commit d690bfa

Please sign in to comment.