Merge branch 'warnings-cleanup' into 'develop'

Cleanup most build warnings.

See merge request funkwhale/funkwhale-android!211
This commit is contained in:
Ryan Harg 2022-12-07 10:24:03 +00:00
commit 10e67f1e80
22 changed files with 184 additions and 181 deletions

View File

@ -36,6 +36,8 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
namespace = "audio.funkwhale.ffa"
testCoverage { testCoverage {
version = "0.8.7" version = "0.8.7"
} }
@ -52,7 +54,7 @@ android {
disable += listOf("MissingTranslation", "ExtraTranslation") disable += listOf("MissingTranslation", "ExtraTranslation")
} }
compileSdk = 31 compileSdk = 33
defaultConfig { defaultConfig {
@ -62,7 +64,7 @@ android {
versionName = androidGitVersion.name() versionName = androidGitVersion.name()
minSdk = 24 minSdk = 24
targetSdk = 30 targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -167,18 +169,18 @@ dependencies {
implementation("com.google.android.material:material:1.6.1") implementation("com.google.android.material:material:1.6.1")
implementation("com.android.support.constraint:constraint-layout:2.0.4") implementation("com.android.support.constraint:constraint-layout:2.0.4")
implementation("com.google.android.exoplayer:exoplayer-core:2.14.2") implementation("com.google.android.exoplayer:exoplayer-core:2.18.1")
implementation("com.google.android.exoplayer:exoplayer-ui:2.14.2") implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1")
implementation("com.google.android.exoplayer:extension-mediasession:2.14.2") implementation("com.google.android.exoplayer:extension-mediasession:2.18.1")
implementation("io.insert-koin:koin-core:3.1.2") implementation("io.insert-koin:koin-core:3.1.2")
implementation("io.insert-koin:koin-android:3.1.2") implementation("io.insert-koin:koin-android:3.1.2")
testImplementation("io.insert-koin:koin-test:3.1.2") testImplementation("io.insert-koin:koin-test:3.1.2")
implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:2.14.0") { implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:789a4f83169cff5c7a91655bb828fde2cfde671a") {
isTransitive = false isTransitive = false
} }
implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:2.14.0") { implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:789a4f83169cff5c7a91655bb828fde2cfde671a") {
isTransitive = false isTransitive = false
} }

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="audio.funkwhale.ffa">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -22,7 +21,8 @@
android:name=".activities.SplashActivity" android:name=".activities.SplashActivity"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:noHistory="true" android:noHistory="true"
android:screenOrientation="portrait"> android:screenOrientation="portrait"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -61,7 +61,8 @@
<service <service
android:name=".playback.PlayerService" android:name=".playback.PlayerService"
android:foregroundServiceType="mediaPlayback"> android:foregroundServiceType="mediaPlayback"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
@ -80,7 +81,8 @@
</service> </service>
<receiver android:name="androidx.media.session.MediaButtonReceiver"> <receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>

View File

@ -6,13 +6,8 @@ import androidx.appcompat.app.AppCompatDelegate
import audio.funkwhale.ffa.koin.authModule import audio.funkwhale.ffa.koin.authModule
import audio.funkwhale.ffa.koin.exoplayerModule import audio.funkwhale.ffa.koin.exoplayerModule
import audio.funkwhale.ffa.utils.AppContext import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.FFACache import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.Request
import com.preference.PowerPreference import com.preference.PowerPreference
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -28,11 +23,6 @@ class FFA : Application() {
var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null
val eventBus: BroadcastChannel<Event> = BroadcastChannel(10)
val commandBus: BroadcastChannel<Command> = BroadcastChannel(10)
val requestBus: BroadcastChannel<Request> = BroadcastChannel(10)
val progressBus: BroadcastChannel<Triple<Int, Int, Int>> = ConflatedBroadcastChannel()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View File

@ -5,6 +5,7 @@ import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -40,30 +41,25 @@ class LoginActivity : AppCompatActivity() {
limitContainerWidth() limitContainerWidth()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private var resultLauncher =
super.onActivityResult(requestCode, resultCode, data) registerForActivityResult(StartActivityForResult()) { result ->
result.data?.let {
oAuth.exchange(this, it) {
PowerPreference
.getFileByName(AppContext.PREFS_CREDENTIALS)
.setBoolean("anonymous", false)
data?.let { lifecycleScope.launch(Main) {
when (requestCode) { Userinfo.get(this@LoginActivity, oAuth)?.let {
0 -> { startActivity(Intent(this@LoginActivity, MainActivity::class.java))
oAuth.exchange(this, data) {
PowerPreference
.getFileByName(AppContext.PREFS_CREDENTIALS)
.setBoolean("anonymous", false)
lifecycleScope.launch(Main) { return@launch finish()
Userinfo.get(this@LoginActivity, oAuth)?.let {
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
return@launch finish()
}
throw Exception(getString(R.string.login_error_userinfo))
} }
throw Exception(getString(R.string.login_error_userinfo))
} }
} }
} }
} }
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -134,7 +130,7 @@ class LoginActivity : AppCompatActivity() {
oAuth.init(hostname) oAuth.init(hostname)
return oAuth.register { return oAuth.register {
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("hostname", hostname) PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("hostname", hostname)
oAuth.authorize(this) resultLauncher.launch(oAuth.authorizeIntent(this))
} }
} }

View File

@ -16,6 +16,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.SeekBar import android.widget.SeekBar
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -207,6 +208,21 @@ class MainActivity : AppCompatActivity() {
return true return true
} }
var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == ResultCode.LOGOUT.code) {
Intent(this, LoginActivity::class.java).apply {
FFA.get().deleteAllData(this@MainActivity)
flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
stopService(Intent(this@MainActivity, PlayerService::class.java))
startActivity(this)
finish()
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
@ -228,8 +244,8 @@ class MainActivity : AppCompatActivity() {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW)
item.actionView = View(this) item.actionView = View(this)
item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?) = false override fun onMenuItemActionExpand(item: MenuItem) = false
override fun onMenuItemActionCollapse(item: MenuItem?) = false override fun onMenuItemActionCollapse(item: MenuItem) = false
}) })
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
@ -279,29 +295,12 @@ class MainActivity : AppCompatActivity() {
} }
} }
R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java)) R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java))
R.id.settings -> startActivityForResult(Intent(this, SettingsActivity::class.java), 0) R.id.settings -> resultLauncher.launch(Intent(this, SettingsActivity::class.java))
} }
return true return true
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == ResultCode.LOGOUT.code) {
Intent(this, LoginActivity::class.java).apply {
FFA.get().deleteAllData(this@MainActivity)
flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
stopService(Intent(this@MainActivity, PlayerService::class.java))
startActivity(this)
finish()
}
}
}
private fun launchFragment(fragment: Fragment) { private fun launchFragment(fragment: Fragment) {
supportFragmentManager.fragments.lastOrNull()?.also { oldFragment -> supportFragmentManager.fragments.lastOrNull()?.also { oldFragment ->
oldFragment.enterTransition = null oldFragment.enterTransition = null
@ -359,7 +358,7 @@ class MainActivity : AppCompatActivity() {
.alpha(0.0f) .alpha(0.0f)
.setDuration(400) .setDuration(400)
.setListener(object : AnimatorListenerAdapter() { .setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator?) { override fun onAnimationEnd(animator: Animator) {
binding.nowPlaying.visibility = View.GONE binding.nowPlaying.visibility = View.GONE
} }
}) })
@ -474,11 +473,9 @@ class MainActivity : AppCompatActivity() {
binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title
binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name
binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.pause)
binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title
binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name
binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon = getDrawable(R.drawable.pause)
Picasso.get() Picasso.get()
.maybeLoad(maybeNormalizeUrl(track.album?.cover?.urls?.original)) .maybeLoad(maybeNormalizeUrl(track.album?.cover?.urls?.original))

View File

@ -59,7 +59,7 @@ class SettingsFragment :
} }
override fun onPreferenceTreeClick(preference: Preference): Boolean { override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference?.key) { when (preference.key) {
"oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java)) "oss_licences" -> startActivity(Intent(activity, LicencesActivity::class.java))
"crash" -> { "crash" -> {

View File

@ -1,8 +1,7 @@
package audio.funkwhale.ffa.adapters package audio.funkwhale.ffa.adapters
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.fragment.app.FragmentPagerAdapter
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.fragments.AlbumsGridFragment import audio.funkwhale.ffa.fragments.AlbumsGridFragment
import audio.funkwhale.ffa.fragments.ArtistsFragment import audio.funkwhale.ffa.fragments.ArtistsFragment
@ -10,13 +9,13 @@ import audio.funkwhale.ffa.fragments.FavoritesFragment
import audio.funkwhale.ffa.fragments.PlaylistsFragment import audio.funkwhale.ffa.fragments.PlaylistsFragment
import audio.funkwhale.ffa.fragments.RadiosFragment import audio.funkwhale.ffa.fragments.RadiosFragment
class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) : class BrowseTabsAdapter(val context: Fragment) :
FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { FragmentStateAdapter(context) {
var tabs = mutableListOf<Fragment>() var tabs = mutableListOf<Fragment>()
override fun getCount() = 5 override fun getItemCount() = 5
override fun getItem(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
tabs.getOrNull(position)?.let { tabs.getOrNull(position)?.let {
return it return it
} }
@ -35,7 +34,7 @@ class BrowseTabsAdapter(val context: Fragment, manager: FragmentManager) :
return fragment return fragment
} }
override fun getPageTitle(position: Int): String { fun tabText(position: Int): String {
return when (position) { return when (position) {
0 -> context.getString(R.string.artists) 0 -> context.getString(R.string.artists)
1 -> context.getString(R.string.albums) 1 -> context.getString(R.string.albums)

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import audio.funkwhale.ffa.adapters.BrowseTabsAdapter import audio.funkwhale.ffa.adapters.BrowseTabsAdapter
import audio.funkwhale.ffa.databinding.FragmentBrowseBinding import audio.funkwhale.ffa.databinding.FragmentBrowseBinding
import com.google.android.material.tabs.TabLayoutMediator
class BrowseFragment : Fragment() { class BrowseFragment : Fragment() {
@ -17,7 +18,7 @@ class BrowseFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
adapter = BrowseTabsAdapter(this, childFragmentManager) adapter = BrowseTabsAdapter(this)
} }
override fun onCreateView( override fun onCreateView(
@ -27,11 +28,13 @@ class BrowseFragment : Fragment() {
): View { ): View {
_binding = FragmentBrowseBinding.inflate(inflater) _binding = FragmentBrowseBinding.inflate(inflater)
return binding.root.apply { return binding.root.apply {
binding.tabs.setupWithViewPager(binding.pager)
binding.tabs.getTabAt(0)?.select() binding.tabs.getTabAt(0)?.select()
binding.pager.adapter = adapter binding.pager.adapter = adapter
binding.pager.offscreenPageLimit = 3 binding.pager.offscreenPageLimit = 3
TabLayoutMediator(binding.tabs, binding.pager) { tab, position ->
tab.text = adapter?.tabText(position)
}.attach()
} }
} }

View File

@ -6,7 +6,7 @@ import audio.funkwhale.ffa.playback.MediaSession
import audio.funkwhale.ffa.utils.AuthorizationServiceFactory import audio.funkwhale.ffa.utils.AuthorizationServiceFactory
import audio.funkwhale.ffa.utils.OAuth import audio.funkwhale.ffa.utils.OAuth
import com.google.android.exoplayer2.database.DatabaseProvider import com.google.android.exoplayer2.database.DatabaseProvider
import com.google.android.exoplayer2.database.ExoDatabaseProvider import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.offline.DownloadManager import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.upstream.cache.Cache import com.google.android.exoplayer2.upstream.cache.Cache
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
@ -19,7 +19,7 @@ import org.koin.dsl.module
fun exoplayerModule(context: Context) = module { fun exoplayerModule(context: Context) = module {
single<DatabaseProvider>(named("exoDatabase")) { single<DatabaseProvider>(named("exoDatabase")) {
ExoDatabaseProvider(context) StandaloneDatabaseProvider(context)
} }
single { single {

View File

@ -2,6 +2,7 @@ package audio.funkwhale.ffa.playback
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
@ -21,7 +22,11 @@ import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
class MediaControlsManager(val context: Service, private val scope: CoroutineScope, private val mediaSession: MediaSessionCompat) { class MediaControlsManager(
val context: Service,
private val scope: CoroutineScope,
private val mediaSession: MediaSessionCompat
) {
companion object { companion object {
const val NOTIFICATION_ACTION_OPEN_QUEUE = 0 const val NOTIFICATION_ACTION_OPEN_QUEUE = 0
@ -41,8 +46,10 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
} }
scope.launch(Default) { scope.launch(Default) {
val openIntent = Intent(context, MainActivity::class.java).apply { action = NOTIFICATION_ACTION_OPEN_QUEUE.toString() } val openIntent = Intent(context, MainActivity::class.java).apply {
val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, 0) action = NOTIFICATION_ACTION_OPEN_QUEUE.toString()
}
val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, FLAG_IMMUTABLE)
val coverUrl = maybeNormalizeUrl(track.album?.cover()) val coverUrl = maybeNormalizeUrl(track.album?.cover())
@ -98,7 +105,8 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
if (playing) { if (playing) {
context.startForeground(AppContext.NOTIFICATION_MEDIA_CONTROL, it) context.startForeground(AppContext.NOTIFICATION_MEDIA_CONTROL, it)
} else { } else {
NotificationManagerCompat.from(context).notify(AppContext.NOTIFICATION_MEDIA_CONTROL, it) NotificationManagerCompat.from(context)
.notify(AppContext.NOTIFICATION_MEDIA_CONTROL, it)
} }
} }

View File

@ -9,7 +9,6 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import audio.funkwhale.ffa.utils.Command import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus import audio.funkwhale.ffa.utils.CommandBus
import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
@ -31,7 +30,6 @@ class MediaSession(private val context: Context) {
val session: MediaSessionCompat by lazy { val session: MediaSessionCompat by lazy {
MediaSessionCompat(context, context.packageName).apply { MediaSessionCompat(context, context.packageName).apply {
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
setPlaybackState(playbackStateBuilder.build()) setPlaybackState(playbackStateBuilder.build())
isActive = true isActive = true
@ -43,7 +41,7 @@ class MediaSession(private val context: Context) {
MediaSessionConnector(session).also { MediaSessionConnector(session).also {
it.setQueueNavigator(FFAQueueNavigator()) it.setQueueNavigator(FFAQueueNavigator())
it.setMediaButtonEventHandler { _, _, intent -> it.setMediaButtonEventHandler { _, intent ->
if (!active) { if (!active) {
Intent(context, PlayerService::class.java).let { player -> Intent(context, PlayerService::class.java).let { player ->
player.action = intent.action player.action = intent.action
@ -67,13 +65,11 @@ class MediaSession(private val context: Context) {
} }
class FFAQueueNavigator : MediaSessionConnector.QueueNavigator { class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) { override fun onSkipToQueueItem(player: Player, id: Long) {
CommandBus.send(Command.PlayTrack(id.toInt())) CommandBus.send(Command.PlayTrack(id.toInt()))
} }
override fun onCurrentWindowIndexChanged(player: Player) {} override fun onCommand(player: Player, command: String, extras: Bundle?, cb: ResultReceiver?) = true
override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true
override fun getSupportedQueueNavigatorActions(player: Player): Long { override fun getSupportedQueueNavigatorActions(player: Player): Long {
return PlaybackStateCompat.ACTION_PLAY_PAUSE or return PlaybackStateCompat.ACTION_PLAY_PAUSE or
@ -82,13 +78,13 @@ class FFAQueueNavigator : MediaSessionConnector.QueueNavigator {
PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
} }
override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) { override fun onSkipToNext(player: Player) {
CommandBus.send(Command.NextTrack) CommandBus.send(Command.NextTrack)
} }
override fun getActiveQueueItemId(player: Player?) = player?.currentWindowIndex?.toLong() ?: 0 override fun getActiveQueueItemId(player: Player?) = player?.currentMediaItemIndex?.toLong() ?: 0
override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) { override fun onSkipToPrevious(player: Player) {
CommandBus.send(Command.PreviousTrack) CommandBus.send(Command.PreviousTrack)
} }

View File

@ -80,14 +80,20 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) {
override fun getScheduler(): Scheduler? = null override fun getScheduler(): Scheduler? = null
override fun getForegroundNotification(downloads: MutableList<Download>): Notification { override fun getForegroundNotification(
downloads: MutableList<Download>,
notMetRequirements: Int
): Notification {
val description = val description =
resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size) resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size)
return DownloadNotificationHelper( return DownloadNotificationHelper(
this, this,
AppContext.NOTIFICATION_CHANNEL_DOWNLOADS AppContext.NOTIFICATION_CHANNEL_DOWNLOADS
).buildProgressNotification(this, R.drawable.downloads, null, description, downloads) ).buildProgressNotification(
this, R.drawable.downloads, null, description,
downloads, notMetRequirements
)
} }
private fun getDownloads() = downloadManager.downloadIndex.getDownloads() private fun getDownloads() = downloadManager.downloadIndex.getDownloads()

View File

@ -31,11 +31,10 @@ import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.maybeNormalizeUrl import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.onApi import audio.funkwhale.ffa.utils.onApi
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlaybackException import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.Tracks
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -65,7 +64,7 @@ class PlayerService : Service() {
private lateinit var queue: QueueManager private lateinit var queue: QueueManager
private lateinit var mediaControlsManager: MediaControlsManager private lateinit var mediaControlsManager: MediaControlsManager
private lateinit var player: SimpleExoPlayer private lateinit var player: ExoPlayer
private val mediaMetadataBuilder = MediaMetadataCompat.Builder() private val mediaMetadataBuilder = MediaMetadataCompat.Builder()
@ -132,12 +131,13 @@ class PlayerService : Service() {
mediaControlsManager = MediaControlsManager(this, scope, mediaSession.session) mediaControlsManager = MediaControlsManager(this, scope, mediaSession.session)
player = SimpleExoPlayer.Builder(this).build().apply { player = ExoPlayer.Builder(this).build().apply {
playWhenReady = false playWhenReady = false
playerEventListener = PlayerEventListener().also { playerEventListener = PlayerEventListener().also {
addListener(it) addListener(it)
} }
EventBus.send(Event.StateChanged(this.isPlaying()))
} }
mediaSession.active = true mediaSession.active = true
@ -151,7 +151,8 @@ class PlayerService : Service() {
} }
if (queue.current > -1) { if (queue.current > -1) {
player.prepare(queue.dataSources) player.setMediaSource(queue.dataSources)
player.prepare()
FFACache.getLine(this, "progress")?.let { FFACache.getLine(this, "progress")?.let {
player.seekTo(queue.current, it.toLong()) player.seekTo(queue.current, it.toLong())
@ -180,7 +181,8 @@ class PlayerService : Service() {
if (!command.fromRadio) radioPlayer.stop() if (!command.fromRadio) radioPlayer.stop()
queue.replace(command.queue) queue.replace(command.queue)
player.prepare(queue.dataSources, true, true) player.setMediaSource(queue.dataSources)
player.prepare()
setPlaybackState(true) setPlaybackState(true)
@ -307,7 +309,8 @@ class PlayerService : Service() {
} }
if (state && player.playbackState == Player.STATE_IDLE) { if (state && player.playbackState == Player.STATE_IDLE) {
player.prepare(queue.dataSources) player.setMediaSource(queue.dataSources)
player.prepare()
} }
if (hasAudioFocus(state)) { if (hasAudioFocus(state)) {
@ -318,7 +321,7 @@ class PlayerService : Service() {
} }
private fun togglePlayback() { private fun togglePlayback() {
setPlaybackState(!player.playWhenReady) setPlaybackState(!player.isPlaying)
} }
private fun skipToPreviousTrack() { private fun skipToPreviousTrack() {
@ -326,11 +329,11 @@ class PlayerService : Service() {
return player.seekTo(0) return player.seekTo(0)
} }
player.previous() player.seekToPrevious()
} }
private fun skipToNextTrack() { private fun skipToNextTrack() {
player.next() player.seekToNext()
FFACache.set(this@PlayerService, "progress", "0") FFACache.set(this@PlayerService, "progress", "0")
ProgressBus.send(0, 0, 0) ProgressBus.send(0, 0, 0)
@ -419,9 +422,14 @@ class PlayerService : Service() {
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")
inner class PlayerEventListener : Player.EventListener { inner class PlayerEventListener : Player.Listener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onPlayerStateChanged(playWhenReady, playbackState) super.onIsPlayingChanged(isPlaying)
mediaControlsManager.updateNotification(queue.current(), isPlaying)
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
super.onPlayWhenReadyChanged(playWhenReady, reason)
EventBus.send(Event.StateChanged(playWhenReady)) EventBus.send(Event.StateChanged(playWhenReady))
@ -429,55 +437,45 @@ class PlayerService : Service() {
CommandBus.send(Command.RefreshTrack(queue.current())) CommandBus.send(Command.RefreshTrack(queue.current()))
} }
when (playWhenReady) { if (!playWhenReady) {
true -> { Build.VERSION_CODES.N.onApi(
when (playbackState) { { stopForeground(STOP_FOREGROUND_DETACH) },
Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true) { stopForeground(false) }
Player.STATE_BUFFERING -> EventBus.send(Event.Buffering(true)) )
Player.STATE_ENDED -> { }
setPlaybackState(false) }
queue.current = 0 override fun onPlaybackStateChanged(playbackState: Int) {
player.seekTo(0, C.TIME_UNSET) super.onPlaybackStateChanged(playbackState)
EventBus.send(Event.Buffering(playbackState == Player.STATE_BUFFERING))
when (playbackState) {
Player.STATE_ENDED -> {
setPlaybackState(false)
ProgressBus.send(0, 0, 0) queue.current = 0
} player.seekTo(0, C.TIME_UNSET)
Player.STATE_IDLE -> { ProgressBus.send(0, 0, 0)
setPlaybackState(false)
return EventBus.send(Event.PlaybackStopped)
}
}
if (playbackState != Player.STATE_BUFFERING) EventBus.send(Event.Buffering(false))
} }
false -> { Player.STATE_IDLE -> {
EventBus.send(Event.Buffering(false)) setPlaybackState(false)
Build.VERSION_CODES.N.onApi( EventBus.send(Event.PlaybackStopped)
{ stopForeground(STOP_FOREGROUND_DETACH) },
{ stopForeground(false) }
)
when (playbackState) { if (!player.playWhenReady) {
Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), false) mediaControlsManager.remove()
Player.STATE_IDLE -> mediaControlsManager.remove()
} }
} }
} }
} }
override fun onTracksChanged( override fun onTracksChanged(tracks: Tracks) {
trackGroups: TrackGroupArray, super.onTracksChanged(tracks)
trackSelections: TrackSelectionArray
) {
super.onTracksChanged(trackGroups, trackSelections)
if (queue.current != player.currentWindowIndex) { if (queue.current != player.currentMediaItemIndex) {
queue.current = player.currentWindowIndex queue.current = player.currentMediaItemIndex
mediaControlsManager.updateNotification(queue.current(), player.playWhenReady) mediaControlsManager.updateNotification(queue.current(), player.isPlaying)
} }
if (queue.get().isNotEmpty() && if (queue.get().isNotEmpty() &&
@ -510,13 +508,14 @@ class PlayerService : Service() {
} }
} }
override fun onPlayerError(error: ExoPlaybackException) { override fun onPlayerError(error: PlaybackException) {
EventBus.send(Event.PlaybackError(getString(R.string.error_playback))) EventBus.send(Event.PlaybackError(getString(R.string.error_playback)))
if (player.playWhenReady) { if (player.playWhenReady) {
queue.current++ queue.current++
player.prepare(queue.dataSources, true, true) player.setMediaSource(queue.dataSources, true)
player.seekTo(queue.current, 0) player.seekTo(queue.current, 0)
player.prepare()
CommandBus.send(Command.RefreshTrack(queue.current())) CommandBus.send(Command.RefreshTrack(queue.current()))
} }

View File

@ -12,6 +12,7 @@ import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.log import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.mustNormalizeUrl import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.github.kittinunf.fuel.gson.gsonDeserializerOf import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.source.ConcatenatingMediaSource import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.gson.Gson import com.google.gson.Gson
@ -38,8 +39,8 @@ class QueueManager(val context: Context) {
metadata.map { track -> metadata.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
ProgressiveMediaSource.Factory(factory).setTag(track.title) val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
.createMediaSource(Uri.parse(url)) ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
} }
) )
} }
@ -63,8 +64,8 @@ class QueueManager(val context: Context) {
val factory = cacheDataSourceFactoryProvider.create(context) val factory = cacheDataSourceFactoryProvider.create(context)
val sources = tracks.map { track -> val sources = tracks.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url)) ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
} }
metadata = tracks.toMutableList() metadata = tracks.toMutableList()
@ -84,7 +85,8 @@ class QueueManager(val context: Context) {
val sources = missingTracks.map { track -> val sources = missingTracks.map { track ->
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)) val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem)
} }
metadata.addAll(tracks) metadata.addAll(tracks)
@ -101,7 +103,8 @@ class QueueManager(val context: Context) {
val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
if (metadata.indexOf(track) == -1) { if (metadata.indexOf(track) == -1) {
ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let { val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build()
ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem).let {
dataSources.addMediaSource(current + 1, it) dataSources.addMediaSource(current + 1, it)
metadata.add(current + 1, track) metadata.add(current + 1, track)
} }

View File

@ -11,8 +11,8 @@ import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.model.TracksCache import audio.funkwhale.ffa.model.TracksCache
import audio.funkwhale.ffa.model.TracksResponse import audio.funkwhale.ffa.model.TracksResponse
import audio.funkwhale.ffa.utils.OAuth import audio.funkwhale.ffa.utils.OAuth
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import audio.funkwhale.ffa.utils.mustNormalizeUrl import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.offline.DownloadManager import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.upstream.cache.Cache import com.google.android.exoplayer2.upstream.cache.Cache
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken

View File

@ -7,8 +7,8 @@ import audio.funkwhale.ffa.model.TracksCache
import audio.funkwhale.ffa.model.TracksResponse import audio.funkwhale.ffa.model.TracksResponse
import audio.funkwhale.ffa.utils.OAuth import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.getMetadata import audio.funkwhale.ffa.utils.getMetadata
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import audio.funkwhale.ffa.utils.mustNormalizeUrl import audio.funkwhale.ffa.utils.mustNormalizeUrl
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadManager import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.upstream.cache.Cache import com.google.android.exoplayer2.upstream.cache.Cache

View File

@ -1,6 +1,5 @@
package audio.funkwhale.ffa.utils package audio.funkwhale.ffa.utils
import audio.funkwhale.ffa.FFA
import audio.funkwhale.ffa.model.Radio import audio.funkwhale.ffa.model.Radio
import audio.funkwhale.ffa.model.Track import audio.funkwhale.ffa.model.Track
import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.Download
@ -8,8 +7,10 @@ import com.google.android.exoplayer2.offline.DownloadCursor
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
sealed class Command { sealed class Command {
@ -71,47 +72,53 @@ sealed class Response {
} }
object EventBus { object EventBus {
private var _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow()
fun send(event: Event) { fun send(event: Event) {
GlobalScope.launch(IO) { GlobalScope.launch(IO) {
FFA.get().eventBus.trySend(event).isSuccess _events.emit(event)
} }
} }
fun get() = FFA.get().eventBus.asFlow() fun get() = events
} }
object CommandBus { object CommandBus {
private var _commands = MutableSharedFlow<Command>()
var commands = _commands.asSharedFlow()
fun send(command: Command) { fun send(command: Command) {
GlobalScope.launch(IO) { GlobalScope.launch(IO) {
FFA.get().commandBus.trySend(command).isSuccess _commands.emit(command)
} }
} }
fun get() = FFA.get().commandBus.asFlow() fun get() = commands
} }
object RequestBus { object RequestBus {
private var _requests = MutableSharedFlow<Request>()
var requests = _requests.asSharedFlow()
fun send(request: Request): Channel<Response> { fun send(request: Request): Channel<Response> {
return Channel<Response>().also { return Channel<Response>().also {
GlobalScope.launch(IO) { GlobalScope.launch(IO) {
request.channel = it request.channel = it
FFA.get().requestBus.trySend(request).isSuccess _requests.emit(request)
} }
} }
} }
fun get() = FFA.get().requestBus.asFlow() fun get() = requests
} }
object ProgressBus { object ProgressBus {
private var _progress = MutableStateFlow(Triple(0, 0, 0))
val progress = _progress.asStateFlow()
fun send(current: Int, duration: Int, percent: Int) { fun send(current: Int, duration: Int, percent: Int) {
GlobalScope.launch(IO) { _progress.value = Triple(current, duration, percent)
FFA.get().progressBus.send(Triple(current, duration, percent))
}
} }
fun get() = FFA.get().progressBus.asFlow().conflate() fun get() = progress
} }
suspend inline fun <reified T> Channel<Response>.wait(): T? { suspend inline fun <reified T> Channel<Response>.wait(): T? {

View File

@ -10,10 +10,8 @@ import audio.funkwhale.ffa.model.DownloadInfo
import audio.funkwhale.ffa.repositories.Repository import audio.funkwhale.ffa.repositories.Repository
import com.github.kittinunf.fuel.core.FuelError import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.Request import com.github.kittinunf.fuel.core.Request
import com.github.kittinunf.fuel.core.ResponseDeserializable
import com.google.android.exoplayer2.offline.Download import com.google.android.exoplayer2.offline.Download
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import com.squareup.picasso.RequestCreator import com.squareup.picasso.RequestCreator
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
@ -23,7 +21,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.openid.appauth.ClientSecretPost import net.openid.appauth.ClientSecretPost
import java.io.Reader
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext

View File

@ -184,11 +184,10 @@ class OAuth(private val authorizationServiceFactory: AuthorizationServiceFactory
) )
} }
fun authorize(activity: Activity) { fun authorizeIntent(activity: Activity): Intent? {
val authService = service(activity) val authService = service(activity)
authorizationRequest()?.let { it -> return authorizationRequest()?.let { it ->
val intent = authService.getAuthorizationRequestIntent(it) authService.getAuthorizationRequestIntent(it)
activity.startActivityForResult(intent, 0)
} }
} }

View File

@ -52,7 +52,7 @@ class NowPlayingView : MaterialCardView {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
gestureDetectorCallback = OnGestureDetection() gestureDetectorCallback = OnGestureDetection()
gestureDetector = GestureDetector(context, gestureDetectorCallback) gestureDetector = GestureDetector(context, gestureDetectorCallback!!)
setOnTouchListener { _, motionEvent -> setOnTouchListener { _, motionEvent ->
val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false
@ -128,8 +128,8 @@ class NowPlayingView : MaterialCardView {
} }
override fun onFling( override fun onFling(
firstMotionEvent: MotionEvent?, firstMotionEvent: MotionEvent,
secondMotionEvent: MotionEvent?, secondMotionEvent: MotionEvent,
velocityX: Float, velocityX: Float,
velocityY: Float velocityY: Float
): Boolean { ): Boolean {
@ -195,7 +195,7 @@ class NowPlayingView : MaterialCardView {
return true return true
} }
override fun onSingleTapUp(e: MotionEvent?): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
layoutParams.let { layoutParams.let {
if (height != minHeight) return true if (height != minHeight) return true

View File

@ -17,7 +17,7 @@
app:tabSelectedTextColor="@color/controlColor" app:tabSelectedTextColor="@color/controlColor"
app:tabTextColor="@color/colorPrimary" /> app:tabTextColor="@color/colorPrimary" />
<androidx.viewpager.widget.ViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -27,6 +27,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import strikt.api.expectThat import strikt.api.expectThat
import strikt.api.expectThrows import strikt.api.expectThrows
import strikt.assertions.isA
import strikt.assertions.isEqualTo import strikt.assertions.isEqualTo
import strikt.assertions.isFalse import strikt.assertions.isFalse
import strikt.assertions.isNotNull import strikt.assertions.isNotNull
@ -282,7 +283,7 @@ class OAuthTest {
} }
@Test @Test
fun `authorize() should start activity for result`() { fun `authorizeIntent() should return an Intent`() {
mockkStatic(PowerPreference::class) mockkStatic(PowerPreference::class)
every { PowerPreference.getFileByName(any()) } returns mockPreference every { PowerPreference.getFileByName(any()) } returns mockPreference
@ -302,9 +303,7 @@ class OAuthTest {
val activity = mockk<Activity>(relaxed = true) val activity = mockk<Activity>(relaxed = true)
oAuth.authorize(activity) expectThat(oAuth.authorizeIntent(activity)).isNotNull().isA<Intent>()
verify { activity.startActivityForResult(mockkIntent, 0) }
} }
private fun <T> deserializeJson( private fun <T> deserializeJson(