Skip to content

Commit

Permalink
completely revamp application structure
Browse files Browse the repository at this point in the history
  • Loading branch information
brdunn committed Jan 22, 2023
1 parent 4f68761 commit e5cd6ee
Show file tree
Hide file tree
Showing 145 changed files with 2,513 additions and 2,631 deletions.
41 changes: 32 additions & 9 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ plugins {
id 'kotlin-kapt'
id 'androidx.navigation.safeargs.kotlin'
id 'dagger.hilt.android.plugin'
id 'com.google.protobuf' version '0.8.19'
}

android {
namespace 'com.devdunnapps.amplify'

compileSdkVersion 33
buildToolsVersion '33.0.0'
compileSdk 33
buildToolsVersion = "33.0.0"

defaultConfig {
applicationId "com.devdunnapps.amplify"
minSdkVersion 21
targetSdkVersion 33
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"

Expand Down Expand Up @@ -67,15 +68,15 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion"
implementation "androidx.palette:palette-ktx:$paletteVersion"
implementation "androidx.datastore:datastore-preferences:$datastoreVersion"
implementation "androidx.datastore:datastore:$datastoreVersion"
implementation "androidx.core:core-ktx:$androidxCoreVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "androidx.media:media:$mediaVersion"

implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
implementation "androidx.navigation:navigation-compose:$navVersion"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
implementation "androidx.hilt:hilt-navigation-compose:$hiltNavigationComposeVersion"

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
Expand All @@ -93,20 +94,22 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"

implementation "androidx.compose.material:material:$composeVersion"
implementation "androidx.compose.material:material:$composeMaterialVersion"
implementation "androidx.compose.animation:animation:$composeVersion"
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
implementation "androidx.compose.material:material-icons-extended:$composeVersion"
implementation "androidx.compose.material:material-icons-extended:$composeMaterialVersion"
implementation "androidx.compose.compiler:compiler:$composeCompilerVersion"
implementation "androidx.paging:paging-compose:$composePagingVersion"
implementation "androidx.activity:activity-compose:$composeActivityVersion"
implementation "androidx.compose.material3:material3:$composeMaterial3Version"
implementation "androidx.compose.material3:material3-window-size-class:$composeMaterial3Version"
implementation "com.google.android.material:compose-theme-adapter-3:$composeMaterial3ThemeAdapterVersion"
implementation "com.google.accompanist:accompanist-themeadapter-material3:$accompanistVersion"

implementation "io.coil-kt:coil:$coilVersion"
implementation "io.coil-kt:coil-compose:$coilVersion"

implementation "com.google.protobuf:protobuf-kotlin-lite:$protobufVersion"

testImplementation "junit:junit:$junitVersion"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$mockWebserverVersion"
Expand All @@ -115,3 +118,23 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$composeVersion"
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobufVersion"
}

generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}

kotlin {
option 'lite'
}
}
}
}
}
1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

<activity
android:name=".ui.main.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:exported="true"
android:launchMode="singleTop"
Expand Down
30 changes: 29 additions & 1 deletion app/src/main/java/com/devdunnapps/amplify/AmplifyApplication.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
package com.devdunnapps.amplify

import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.devdunnapps.amplify.domain.models.ThemeConfig
import com.devdunnapps.amplify.domain.repository.PreferencesRepository
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltAndroidApp
class AmplifyApplication : Application()
class AmplifyApplication : Application() {

@Inject
lateinit var preferencesRepository: PreferencesRepository

private val applicationScope = MainScope()

override fun onCreate() {
super.onCreate()

// Set the application theme
applicationScope.launch {
preferencesRepository.userData.collect {
when (it.themeConfig) {
ThemeConfig.DARK -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
ThemeConfig.LIGHT -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
ThemeConfig.FOLLOW_SYSTEM ->
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
}
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/devdunnapps/amplify/data/PlexAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ interface PlexAPI {
suspend fun getSongLyrics(
@Path("lyricKey") lyricKey: String,
@Header("X-Plex-Token") userToken: String
): ResponseBody
): PlexModelDTO

@GET("library/sections")
suspend fun getLibrarySections(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class PlexRepositoryImpl @Inject constructor(
override fun getSong(songId: String): Flow<Resource<Song>> = flow {
emit(Resource.Loading())
try {
val song = api.getSong(songId, userToken).mediaContainer.metadata?.get(0)?.toSong()
val song = api.getSong(songId, userToken).mediaContainer.metadata?.get(0)?.toSong() ?: run {
emit(Resource.Error())
return@flow
}
emit(Resource.Success(song))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
Expand All @@ -41,7 +44,10 @@ class PlexRepositoryImpl @Inject constructor(
override fun getAlbum(key: String): Flow<Resource<Album>> = flow {
emit(Resource.Loading())
try {
val album = api.getAlbum(key, userToken).mediaContainer.metadata?.get(0)?.toAlbum()
val album = api.getAlbum(key, userToken).mediaContainer.metadata?.get(0)?.toAlbum() ?: run {
emit(Resource.Error())
return@flow
}
emit(Resource.Success(album))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
Expand Down Expand Up @@ -78,7 +84,10 @@ class PlexRepositoryImpl @Inject constructor(
override fun getPlaylist(playlistId: String): Flow<Resource<Playlist>> = flow {
emit(Resource.Loading())
try {
val playlist = api.getPlaylist(playlistId, userToken).mediaContainer.metadata?.get(0)?.toPlaylist()
val playlist = api.getPlaylist(playlistId, userToken).mediaContainer.metadata?.get(0)?.toPlaylist() ?: run {
emit(Resource.Error())
return@flow
}
emit(Resource.Success(playlist))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
Expand Down Expand Up @@ -115,7 +124,7 @@ class PlexRepositoryImpl @Inject constructor(
}
}

override fun getArtistSinglesEPs(artistKey: String) = flow {
override fun getArtistSinglesEPs(artistKey: String): Flow<Resource<List<Album>>> = flow {
emit(Resource.Loading())
try {
val singlesEPs = api.getArtistSinglesEPs(section, artistKey, userToken).mediaContainer.metadata
Expand Down Expand Up @@ -157,21 +166,21 @@ class PlexRepositoryImpl @Inject constructor(
}
}

override fun addSongToPlaylist(songId: String, playlistId: String): Flow<Resource<Playlist>> = flow {
override fun addSongToPlaylist(songId: String, playlistId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
try {
val serverId = api.getServerIdentity(userToken).mediaContainer.machineIdentifier!!
val songUri = "server://$serverId/com.plexapp.plugins.library/library/metadata/$songId"
api.addSongToPlaylist(playlistId, songUri, userToken)
emit(Resource.Success())
emit(Resource.Success(Unit))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
} catch(e: IOException) {
emit(Resource.Error("Couldn't add song to playlist, please check your internet connection."))
}
}

override fun removeSongFromPlaylist(songId: String, playlistId: String): Flow<Resource<Playlist>> = flow {
override fun removeSongFromPlaylist(songId: String, playlistId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
try {
// Plex uses a unique "playlistItemID" so we must search the playlist for the playlistItemID of the song
Expand All @@ -185,7 +194,7 @@ class PlexRepositoryImpl @Inject constructor(
}

api.removeSongFromPlaylist(playlistId, playlistItemId, userToken)
emit(Resource.Success())
emit(Resource.Success(Unit))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
} catch(e: IOException) {
Expand All @@ -196,14 +205,27 @@ class PlexRepositoryImpl @Inject constructor(
override fun getSongLyrics(songId: String): Flow<Resource<Lyric>> = flow {
emit(Resource.Loading())
try {
val streams = api.getSong(songId, userToken).mediaContainer.metadata!![0].media!![0].part!![0].stream!!
val streams =
api.getSong(songId, userToken).mediaContainer.metadata?.get(0)?.media?.get(0)?.part?.get(0)?.stream

if (streams == null) {
emit(Resource.Error("Lyrics not available for this song."))
return@flow
}

for (stream in streams) {
val isTextLyric = stream.streamType == 4 && stream.format.equals("txt")
if (isTextLyric) {
// TODO: fix blocking call
val rawLyrics = api.getSongLyrics(stream.id!!, userToken).string()
if (stream.streamType == 4) {
val rawLyrics =
api.getSongLyrics(stream.id!!, userToken).mediaContainer.lyrics?.get(0)?.toRawLyrics()

if (rawLyrics == null) {
emit(Resource.Error("Lyrics not available for this song."))
return@flow
}

val lyrics = Lyric(songId, rawLyrics)
emit(Resource.Success(lyrics))
return@flow
}
}
} catch(e: HttpException) {
Expand All @@ -213,12 +235,12 @@ class PlexRepositoryImpl @Inject constructor(
}
}

override fun deletePlaylist(playlistId: String): Flow<Resource<Playlist>> = flow {
override fun deletePlaylist(playlistId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
try {
val apiResponse = api.deletePlaylist(playlistId, userToken)
if (apiResponse.code() == HttpURLConnection.HTTP_NO_CONTENT) {
emit(Resource.Success())
emit(Resource.Success(Unit))
} else {
emit(Resource.Error("Error deleting playlist"))
}
Expand All @@ -232,7 +254,11 @@ class PlexRepositoryImpl @Inject constructor(
override fun createPlaylist(playlistTitle: String): Flow<Resource<Playlist>> = flow {
emit(Resource.Loading())
try {
val apiResponse = api.createPlaylist(playlistTitle, userToken).mediaContainer.metadata?.get(0)?.toPlaylist()
val apiResponse =
api.createPlaylist(playlistTitle, userToken).mediaContainer.metadata?.get(0)?.toPlaylist() ?: run {
emit(Resource.Error())
return@flow
}
emit(Resource.Success(apiResponse))
} catch(e: HttpException) {
emit(Resource.Error("Oops, something went wrong!"))
Expand Down Expand Up @@ -298,7 +324,7 @@ class PlexRepositoryImpl @Inject constructor(
try {
val apiResponse = api.rateSong(songId, rating.toString(), userToken)
if (apiResponse.code() == HttpURLConnection.HTTP_OK) {
emit(Resource.Success())
emit(Resource.Success(Unit))
} else {
emit(Resource.Error("Error rating song"))
}
Expand All @@ -314,7 +340,7 @@ class PlexRepositoryImpl @Inject constructor(
try {
val apiResponse = api.markSongAsListened(songId, userToken)
if (apiResponse.code() == HttpURLConnection.HTTP_OK) {
emit(Resource.Success())
emit(Resource.Success(Unit))
} else {
emit(Resource.Error("Could not mark as listened"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ class PlexTVRepositoryImpl @Inject constructor(
val userCredentials = SigninDTO(username, password, authToken)
val response = api.signInUser(userCredentials)
if (response.code() == 201) {
emit(Resource.Success(response.body()?.toUser()))
val user = response.body()?.toUser() ?: run {
emit(Resource.Error())
return@flow
}
emit(Resource.Success(user))
} else if (response.code() == 401) {
val errors = Gson().fromJson(response.errorBody()?.charStream(), ErrorsDTO::class.java)
if (errors.errors[0].code == 1029) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
package com.devdunnapps.amplify.data

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.devdunnapps.amplify.ThemeConfigProto
import com.devdunnapps.amplify.UserPreferences
import com.devdunnapps.amplify.copy
import com.devdunnapps.amplify.domain.models.Preferences
import com.devdunnapps.amplify.domain.models.ThemeConfig
import com.devdunnapps.amplify.domain.repository.PreferencesRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class PreferencesRepositoryImpl @Inject constructor (
private val context: Context
private val preferences: DataStore<UserPreferences>
): PreferencesRepository {

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "amplify_settings")

override suspend fun write(key: String, value: String) {
context.dataStore.edit { preferences ->
preferences[stringPreferencesKey(key)] = value
override val userData = preferences.data
.map {
Preferences(
themeConfig = when (it.themeConfig) {
ThemeConfigProto.DARK_THEME_CONFIG_DARK -> ThemeConfig.DARK
ThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> ThemeConfig.LIGHT
else -> ThemeConfig.FOLLOW_SYSTEM
}
)
}
}

override suspend fun writeBoolean(key: String, value: Boolean) {
context.dataStore.edit { preferences ->
preferences[booleanPreferencesKey(key)] = value
override suspend fun setTheme(themeConfig: ThemeConfig) {
preferences.updateData {
it.copy {
this.themeConfig = when (themeConfig) {
ThemeConfig.FOLLOW_SYSTEM -> ThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM
ThemeConfig.LIGHT -> ThemeConfigProto.DARK_THEME_CONFIG_LIGHT
ThemeConfig.DARK -> ThemeConfigProto.DARK_THEME_CONFIG_DARK
}
}
}
}

override suspend fun read(key: String): String? {
return context.dataStore.data.first()[stringPreferencesKey(key)]
}

override suspend fun readBoolean(key: String): Boolean {
return context.dataStore.data.first()[booleanPreferencesKey(key)] ?: true
}
}
Loading

0 comments on commit e5cd6ee

Please sign in to comment.