Skip to content

Commit 197b15e

Browse files
committed
Initial Commit
0 parents  commit 197b15e

58 files changed

Lines changed: 1624 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Gradle files
2+
.gradle/
3+
build/
4+
5+
# Local configuration file (sdk path, etc)
6+
local.properties
7+
8+
# Log/OS Files
9+
*.log
10+
11+
# Android Studio generated files and folders
12+
captures/
13+
.externalNativeBuild/
14+
.cxx/
15+
*.apk
16+
output.json
17+
18+
# IntelliJ
19+
*.iml
20+
.idea/
21+
misc.xml
22+
deploymentTargetDropDown.xml
23+
render.experimental.xml
24+
25+
# Keystore files
26+
*.jks
27+
*.keystore
28+
29+
# Google Services (e.g. APIs or Firebase)
30+
google-services.json
31+
32+
# Android Profiling
33+
*.hprof

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Carles Sentis
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Deepcuts.fm-Android
2+
Listen to Deepcuts.fm room on the background on Android

app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

app/build.gradle.kts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
plugins {
2+
id("com.android.application")
3+
id("org.jetbrains.kotlin.android")
4+
id("com.google.devtools.ksp")
5+
id("com.google.dagger.hilt.android")
6+
}
7+
8+
android {
9+
namespace = "com.codeskraps.deepcuts"
10+
compileSdk = 34
11+
12+
defaultConfig {
13+
applicationId = "com.codeskraps.deepcuts"
14+
minSdk = 26
15+
targetSdk = 34
16+
versionCode = 1
17+
versionName = "1.0"
18+
19+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20+
vectorDrawables {
21+
useSupportLibrary = true
22+
}
23+
}
24+
25+
buildTypes {
26+
release {
27+
isMinifyEnabled = false
28+
proguardFiles(
29+
getDefaultProguardFile("proguard-android-optimize.txt"),
30+
"proguard-rules.pro"
31+
)
32+
}
33+
}
34+
compileOptions {
35+
sourceCompatibility = JavaVersion.VERSION_17
36+
targetCompatibility = JavaVersion.VERSION_17
37+
}
38+
kotlinOptions {
39+
jvmTarget = "17"
40+
}
41+
buildFeatures {
42+
compose = true
43+
}
44+
composeOptions {
45+
kotlinCompilerExtensionVersion = "1.5.7"
46+
}
47+
packaging {
48+
resources {
49+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
50+
}
51+
}
52+
53+
}
54+
55+
dependencies {
56+
57+
implementation("androidx.core:core-ktx:1.12.0")
58+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
59+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
60+
implementation("androidx.activity:activity-compose:1.8.2")
61+
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
62+
implementation("androidx.compose.ui:ui")
63+
implementation("androidx.compose.ui:ui-graphics")
64+
implementation("androidx.compose.ui:ui-tooling-preview")
65+
implementation("androidx.compose.material3:material3")
66+
67+
//Dagger - Hilt
68+
implementation("com.google.dagger:hilt-android:2.50")
69+
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
70+
ksp("com.google.dagger:hilt-android-compiler:2.50")
71+
72+
testImplementation("junit:junit:4.13.2")
73+
androidTestImplementation("androidx.test.ext:junit:1.1.5")
74+
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
75+
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
76+
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
77+
debugImplementation("androidx.compose.ui:ui-tooling")
78+
debugImplementation("androidx.compose.ui:ui-test-manifest")
79+
}

app/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.codeskraps.deepcuts
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
9+
import org.junit.Assert.*
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* See [testing documentation](http://d.android.com/tools/testing).
15+
*/
16+
@RunWith(AndroidJUnit4::class)
17+
class ExampleInstrumentedTest {
18+
@Test
19+
fun useAppContext() {
20+
// Context of the app under test.
21+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22+
assertEquals("com.trifork.turntable", appContext.packageName)
23+
}
24+
}

app/src/main/AndroidManifest.xml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
7+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
8+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
9+
10+
<application
11+
android:name=".DeepcutsApp"
12+
android:allowBackup="true"
13+
android:dataExtractionRules="@xml/data_extraction_rules"
14+
android:fullBackupContent="@xml/backup_rules"
15+
android:icon="@mipmap/ic_launcher"
16+
android:label="@string/app_name"
17+
android:supportsRtl="true"
18+
android:theme="@style/Theme.Deepcuts"
19+
tools:targetApi="31">
20+
<activity
21+
android:name=".MainActivity"
22+
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
23+
android:exported="true"
24+
android:launchMode="singleTop"
25+
android:theme="@style/Theme.Deepcuts">
26+
<intent-filter>
27+
<action android:name="android.intent.action.MAIN" />
28+
29+
<category android:name="android.intent.category.LAUNCHER" />
30+
</intent-filter>
31+
</activity>
32+
<service
33+
android:name=".ForegroundService"
34+
android:enabled="true"
35+
android:exported="false"
36+
android:foregroundServiceType="mediaPlayback" />
37+
</application>
38+
39+
</manifest>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.codeskraps.deepcuts
2+
3+
import android.app.Application
4+
import dagger.hilt.android.HiltAndroidApp
5+
6+
@HiltAndroidApp
7+
class DeepcutsApp : Application()
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.codeskraps.deepcuts
2+
3+
import android.app.Notification
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.app.PendingIntent
7+
import android.app.Service
8+
import android.content.Intent
9+
import android.os.IBinder
10+
import android.util.Log
11+
import androidx.core.app.NotificationCompat
12+
import com.codeskraps.deepcuts.util.BackgroundStatus
13+
import com.codeskraps.deepcuts.util.Constants
14+
import com.codeskraps.deepcuts.webview.components.MediaWebView
15+
import dagger.hilt.android.AndroidEntryPoint
16+
import javax.inject.Inject
17+
18+
19+
@AndroidEntryPoint
20+
class ForegroundService : Service() {
21+
companion object {
22+
private val TAG = ForegroundService::class.java.simpleName
23+
private const val NOTIF_ID = 1
24+
private const val CHANNEL_ID = "ForegroundServiceChannel"
25+
private const val DELETE_EXTRA = "deleteExtra"
26+
private const val HOME_EXTRA = "homeExtra"
27+
private const val REFRESH_EXTRA = "refreshExtra"
28+
}
29+
30+
@Inject
31+
lateinit var backgroundStatus: BackgroundStatus
32+
33+
@Inject
34+
lateinit var mediaWebView: MediaWebView
35+
36+
override fun onBind(intent: Intent?): IBinder? = null
37+
38+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
39+
Log.v(TAG, "onStartCommand")
40+
41+
mediaWebView.setUrlListener { url ->
42+
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).run {
43+
notify(NOTIF_ID, createNotification(url))
44+
}
45+
}
46+
47+
var input = ""
48+
49+
intent?.extras?.let {
50+
if (it.getBoolean(DELETE_EXTRA, false)) {
51+
stopSelf()
52+
return START_NOT_STICKY
53+
54+
} else if (it.getBoolean(HOME_EXTRA, false)) {
55+
mediaWebView.loadUrl(Constants.home)
56+
return START_NOT_STICKY
57+
58+
} else if (it.getBoolean(REFRESH_EXTRA, false)) {
59+
mediaWebView.reload()
60+
return START_NOT_STICKY
61+
62+
} else {
63+
input = it.getString(Constants.inputExtra) ?: input
64+
}
65+
}
66+
67+
createNotificationChannel()
68+
startForeground(NOTIF_ID, createNotification(input))
69+
backgroundStatus.setValue(true)
70+
71+
return START_NOT_STICKY
72+
}
73+
74+
override fun onDestroy() {
75+
super.onDestroy()
76+
backgroundStatus.setValue(false)
77+
mediaWebView.setUrlListener(null)
78+
}
79+
80+
private fun createNotificationChannel() {
81+
val serviceChannel = NotificationChannel(
82+
CHANNEL_ID,
83+
"Foreground MediaWebView Channel",
84+
NotificationManager.IMPORTANCE_DEFAULT
85+
)
86+
getSystemService(NotificationManager::class.java).run {
87+
createNotificationChannel(serviceChannel)
88+
}
89+
}
90+
91+
private fun createNotification(contentText: String): Notification =
92+
NotificationCompat.Builder(this, CHANNEL_ID)
93+
.setContentTitle(getString(R.string.app_name))
94+
.setContentText(contentText)
95+
.setSmallIcon(R.drawable.ic_notification)
96+
.setContentIntent(contentPendingIntent())
97+
.setDeleteIntent(deletePendingIntent())
98+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
99+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
100+
.addAction(R.drawable.ic_home, "Home", homePendingIntent())
101+
.addAction(R.drawable.ic_refresh, "Refresh", refreshPendingIntent())
102+
.build()
103+
104+
private fun contentPendingIntent(): PendingIntent = PendingIntent.getActivity(
105+
this,
106+
2,
107+
Intent(this, MainActivity::class.java).apply {
108+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
109+
},
110+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
111+
)
112+
113+
private fun deletePendingIntent(): PendingIntent = PendingIntent.getService(
114+
this,
115+
3,
116+
Intent(this, ForegroundService::class.java).apply {
117+
putExtra(DELETE_EXTRA, true)
118+
},
119+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
120+
)
121+
122+
private fun homePendingIntent(): PendingIntent = PendingIntent.getService(
123+
this,
124+
4,
125+
Intent(this, ForegroundService::class.java).apply {
126+
putExtra(HOME_EXTRA, true)
127+
},
128+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
129+
)
130+
131+
private fun refreshPendingIntent(): PendingIntent = PendingIntent.getService(
132+
this,
133+
5,
134+
Intent(this, ForegroundService::class.java).apply {
135+
putExtra(REFRESH_EXTRA, true)
136+
},
137+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
138+
)
139+
}

0 commit comments

Comments
 (0)