Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/

.DS_Store

# Gradle files
.gradle/
build/
Expand All @@ -30,6 +32,10 @@ proguard/
# Log Files
*.log

# Android Studio
.idea
/.idea/

# Android Studio Navigation editor temp files
.navigation/

Expand All @@ -52,15 +58,15 @@ captures/

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
*.jks
*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/

# Google Services (e.g. APIs or Firebase)
# google-services.json
google-services.json

# Freeline
freeline.py
Expand All @@ -82,4 +88,4 @@ lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
lint/reports/
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# Podcast-Player
#### Podcast Player App

Esse App foi desenvolvido a API [PodcastIndex.org API](https://podcastindex-org.github.io/docs-api/#overview), que é uma API gratuita que provem dados de Podcasts.

Para poder rodar o APP, abra o seguinte arquivo: app/src/main/res/values/podcast_index_api_keys.xml. Nele você deve colocar o seu API secret e sua API key que dá para se obter gratuitamente no site acima.

Esse APP foi desenvolvido seguindo a arquitetura MVC. Segue um diagrama demonstrando como está o app:


<img src="app/src/main/resources/raw/Arch-MVC-renamed.jpg" width="1300" title="i">

Seguem prints do app rodando:

<img src="app/src/main/resources/raw/Screenshot_20210405-023625_One UI Home.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-023636_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-023701_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024328_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024437_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024459_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024522_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024537_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024559_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024612_Podcast Player.jpg" width="300" title="i">
<img src="app/src/main/resources/raw/Screenshot_20210405-024900_Podcast Player.jpg" width="300" title="i">
Empty file added X#
Empty file.
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
68 changes: 68 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
}

android {
compileSdkVersion 30
buildToolsVersion "30.0.2"

defaultConfig {
applicationId "br.ufpe.cin.vrvs.podcastplayer"
minSdkVersion 22
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
}
}

dependencies {
implementation "androidx.appcompat:appcompat:$versions.appcompat"
implementation "androidx.constraintlayout:constraintlayout:$versions.constraintlayout"
implementation "androidx.core:core-ktx:$versions.core_ktx"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$versions.lifecycle"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$versions.lifecycle"
implementation "androidx.navigation:navigation-fragment-ktx:$versions.navigation"
implementation "androidx.navigation:navigation-ui-ktx:$versions.navigation"
implementation "androidx.room:room-ktx:$versions.room"
implementation "androidx.room:room-runtime:$versions.room"
implementation "com.airbnb.android:lottie:$versions.lottie"
implementation "com.google.android.material:material:$versions.material"
implementation "com.squareup.picasso:picasso:$versions.picasso"
implementation "com.squareup.retrofit2:converter-gson:$versions.retrofit"
implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttp"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlin_coroutine"
implementation "org.koin:koin-android:$versions.koin_android"
implementation "org.koin:koin-android-ext:$versions.koin_android"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

kapt "androidx.room:room-compiler:$versions.room"

testImplementation "junit:junit:$versions.junit"

androidTestImplementation "androidx.test.ext:junit:$versions.ext_junit"
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espresso_core"
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package br.ufpe.cin.vrvs.podcastplayer

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("br.ufpe.cin.vrvs.podcastplayer", appContext.packageName)
}
}
40 changes: 40 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.ufpe.cin.vrvs.podcastplayer">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".app.PodcastPlayerApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_podcast_player"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_podcast_player_round"
android:supportsRtl="true"
android:theme="@style/Theme.PodcastPlayer">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".services.player.PodcastPlayerService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
</application>

</manifest>
Binary file added app/src/main/ic_podcast_player-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions app/src/main/java/br/ufpe/cin/vrvs/podcastplayer/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package br.ufpe.cin.vrvs.podcastplayer

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment
import br.ufpe.cin.vrvs.podcastplayer.services.player.PodcastPlayerService.Companion.PODCAST_ID

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val podcastId = intent.extras?.getString(PODCAST_ID)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
if (podcastId != null) {
val action = NavGraphDirections.actionGlobalPodcastDetailsFragment(podcastId)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_graph_container) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(action)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package br.ufpe.cin.vrvs.podcastplayer.app

import android.app.Application
import br.ufpe.cin.vrvs.podcastplayer.di.apiModule
import br.ufpe.cin.vrvs.podcastplayer.di.databaseModule
import br.ufpe.cin.vrvs.podcastplayer.di.preferencesModule
import br.ufpe.cin.vrvs.podcastplayer.di.repositoryModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin

class PodcastPlayerApplication : Application() {

private val modules = listOf(
apiModule,
databaseModule,
preferencesModule,
repositoryModule
)

override fun onCreate() {
super.onCreate()

// Start Koin
startKoin{
androidLogger()
androidContext(this@PodcastPlayerApplication)
modules(modules)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.EpisodePersisted
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.EpisodePersistedDownloaded
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.EpisodePersistedPlaying
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.PodcastPersisted

@Dao
interface PodcastDao {

@Query("SELECT * FROM podcast_table")
suspend fun getPodcasts(): List<PodcastPersisted>

@Query("SELECT * FROM podcast_table where :id = id")
suspend fun getPodcast(id: String): PodcastPersisted

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPodcast(podcast: PodcastPersisted)

@Query("DELETE FROM podcast_table where :id = id")
suspend fun clearPodcast(id: String)

@Query("SELECT EXISTS(SELECT * FROM podcast_table where :id = id)")
fun hasPodcast(id: String): Boolean

@Query("SELECT * FROM episode_table")
suspend fun getEpisodes(): List<EpisodePersisted>

@Query("SELECT * FROM episode_table where :id = id")
suspend fun getEpisode(id: String): EpisodePersisted

@Query("SELECT * FROM episode_table where :podcastId = podcastId order by season, episode")
suspend fun getPodcastEpisodes(podcastId: String): List<EpisodePersisted>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertEpisode(episode: EpisodePersisted)

@Query("DELETE FROM episode_table where :id = id")
suspend fun clearEpisode(id: String)

@Query("DELETE FROM episode_table where :podcastId = podcastId")
suspend fun clearPodcastEpisodes(podcastId: String)

@Query("SELECT EXISTS(SELECT * FROM episode_table where :id = id)")
fun hasEpisode(id: String): Boolean

@Update(entity = EpisodePersisted::class)
suspend fun updateDownloaded(downloaded: EpisodePersistedDownloaded)

@Update(entity = EpisodePersisted::class)
suspend fun updatePlaying(downloaded: EpisodePersistedPlaying)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.EpisodePersisted
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.PodcastPersisted
import br.ufpe.cin.vrvs.podcastplayer.data.datasource.local.database.table.converter.MapConverter

@Database(
entities = [
EpisodePersisted::class,
PodcastPersisted::class
],
version = 1
)
@TypeConverters(MapConverter::class)
abstract class PodcastDatabase : RoomDatabase() {
abstract fun podcastDao(): PodcastDao
}
Loading