mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-24 01:44:32 +01:00
Some more translations, ErrorInfoDialog and Refactoring
This commit is contained in:
parent
fc9355269a
commit
9881cc55aa
@ -49,7 +49,7 @@
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:label="@string/app_name"
|
||||
android:label="SpotiFlyer"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:forceDarkAllowed="true"
|
||||
@ -57,7 +57,7 @@
|
||||
tools:targetApi="q">
|
||||
<activity android:name=".MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/app_name"
|
||||
android:label="SpotiFlyer"
|
||||
android:hardwareAccelerated="true"
|
||||
android:theme="@style/Theme.SpotiFlyer"
|
||||
>
|
||||
|
@ -17,6 +17,8 @@
|
||||
package com.shabinder.spotiflyer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -31,11 +33,20 @@ import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -51,7 +62,10 @@ import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import com.google.accompanist.insets.navigationBarsPadding
|
||||
import com.google.accompanist.insets.statusBarsHeight
|
||||
import com.google.accompanist.insets.statusBarsPadding
|
||||
import com.shabinder.common.di.*
|
||||
import com.shabinder.common.di.ConnectionLiveData
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.observeAsState
|
||||
import com.shabinder.common.di.preference.PreferenceManager
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
@ -63,18 +77,28 @@ import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
|
||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.*
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTheme
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import com.shabinder.common.uikit.screens.SpotiFlyerRootContent
|
||||
import com.shabinder.spotiflyer.service.ForegroundService
|
||||
import com.shabinder.spotiflyer.ui.AnalyticsDialog
|
||||
import com.shabinder.spotiflyer.ui.NetworkDialog
|
||||
import com.shabinder.spotiflyer.ui.PermissionDialog
|
||||
import com.shabinder.spotiflyer.utils.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import com.shabinder.spotiflyer.utils.checkAppSignature
|
||||
import com.shabinder.spotiflyer.utils.checkIfLatestVersion
|
||||
import com.shabinder.spotiflyer.utils.checkPermissions
|
||||
import com.shabinder.spotiflyer.utils.disableDozeMode
|
||||
import com.shabinder.spotiflyer.utils.requestStoragePermission
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.matomo.sdk.extra.TrackHelper
|
||||
import java.io.File
|
||||
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@ -295,6 +319,14 @@ class MainActivity : ComponentActivity() {
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
override fun copyToClipboard(text: String) {
|
||||
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("SpotiFlyer Selection", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
|
||||
showPopUpMessage("StackTrace Copied to Clipboard.")
|
||||
}
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) {
|
||||
val manager: PackageManager = applicationContext.packageManager
|
||||
try {
|
||||
|
@ -27,9 +27,9 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorPrimary
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
|
@ -44,9 +44,9 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorOffWhite
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@ -88,7 +88,8 @@ fun NetworkDialog(
|
||||
) {
|
||||
Image(Icons.Rounded.CloudOff,
|
||||
Strings.noInternetConnection(),Modifier.size(42.dp),colorFilter = ColorFilter.tint(
|
||||
colorOffWhite))
|
||||
colorOffWhite
|
||||
))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Text(
|
||||
text = Strings.checkInternetConnection(),
|
||||
|
@ -29,9 +29,9 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorPrimary
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
|
@ -33,14 +33,14 @@ allprojects {
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
|
||||
}
|
||||
}
|
||||
afterEvaluate {
|
||||
project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt ->
|
||||
kmpExt.sourceSets.run {
|
||||
all {
|
||||
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
}
|
||||
removeAll { it.name == "androidAndroidTestRelease" }
|
||||
}
|
||||
|
@ -30,13 +30,14 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.tools.build:gradle:4.1.1")
|
||||
implementation(Androidx.gradlePlugin)
|
||||
implementation(JetBrains.Compose.gradlePlugin)
|
||||
implementation(JetBrains.Kotlin.gradlePlugin)
|
||||
implementation(JetBrains.Kotlin.serialization)
|
||||
implementation(SqlDelight.gradlePlugin)
|
||||
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}")
|
||||
implementation("de.comahe.i18n4k:i18n4k-gradle-plugin:0.1.1")
|
||||
implementation(KTLint.gradlePlugin)
|
||||
implementation(Internationalization.gradlePlugin)
|
||||
implementation(Mosaic.gradlePlugin)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
@ -19,17 +19,21 @@
|
||||
object Versions {
|
||||
// App's Version (To be bumped at each update)
|
||||
const val versionName = "3.1.0"
|
||||
const val versionCode = 20
|
||||
|
||||
const val versionCode = 20
|
||||
// Kotlin
|
||||
const val kotlinVersion = "1.5.10"
|
||||
|
||||
const val coroutinesVersion = "1.5.0"
|
||||
|
||||
// Code Formatting
|
||||
const val ktLint = "10.1.0"
|
||||
|
||||
// Console-App UI
|
||||
const val mosaic = "0.1.0"
|
||||
|
||||
// DI
|
||||
const val koin = "3.1.0"
|
||||
const val koin = "3.1.2"
|
||||
|
||||
// Logger
|
||||
const val kermit = "0.1.9"
|
||||
@ -45,6 +49,9 @@ object Versions {
|
||||
const val sqliteJdbcDriver = "3.34.0"
|
||||
const val slf4j = "1.7.31"
|
||||
|
||||
// Internationalisation
|
||||
const val i18n4k = "0.1.2"
|
||||
|
||||
// Android
|
||||
const val minSdkVersion = 21
|
||||
const val compileSdkVersion = 29
|
||||
@ -80,15 +87,11 @@ object Androidx {
|
||||
const val junit = "androidx.test.ext:junit:1.1.2"
|
||||
const val expresso = "androidx.test.espresso:espresso-core:3.3.0"
|
||||
|
||||
/*object Compose{
|
||||
const val materialIcon = "androidx.compose.material:material-icons-extended:${Versions.compose}"
|
||||
const val ui = "androidx.compose.ui:ui:${Versions.compose}"
|
||||
const val uiGraphics = "androidx.compose.ui:ui-graphics:${Versions.compose}"
|
||||
const val uiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
|
||||
const val foundationLayout = "androidx.compose.foundation:foundation-layout:${Versions.compose}"
|
||||
const val material = "androidx.compose.material:material:${Versions.compose}"
|
||||
const val runtimeLiveData = "androidx.compose.runtime:runtime-livedata:${Versions.compose}"
|
||||
}*/
|
||||
const val gradlePlugin = "com.android.tools.build:gradle:4.1.1"
|
||||
}
|
||||
|
||||
object KTLint {
|
||||
const val gradlePlugin = "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}"
|
||||
}
|
||||
|
||||
object JetBrains {
|
||||
@ -106,7 +109,9 @@ object JetBrains {
|
||||
const val gradlePlugin = "org.jetbrains.compose:compose-gradle-plugin:$VERSION"
|
||||
}
|
||||
}
|
||||
|
||||
object Mosaic {
|
||||
const val gradlePlugin = "com.jakewharton.mosaic:mosaic-gradle-plugin:${Versions.mosaic}"
|
||||
}
|
||||
object Decompose {
|
||||
private const val VERSION = "0.2.6"
|
||||
const val decompose = "com.arkivanov.decompose:decompose:$VERSION"
|
||||
@ -146,7 +151,8 @@ object Ktor {
|
||||
}
|
||||
|
||||
object Internationalization {
|
||||
const val dep = "de.comahe.i18n4k:i18n4k-core:0.1.1"
|
||||
const val dep = "de.comahe.i18n4k:i18n4k-core:${Versions.i18n4k}"
|
||||
const val gradlePlugin = "de.comahe.i18n4k:i18n4k-gradle-plugin:${Versions.i18n4k}"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun Dialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(isVisible) {
|
||||
androidx.compose.ui.window.Dialog(onDismiss) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ actual fun ImageLoad(
|
||||
link: String,
|
||||
loader: suspend () -> Picture,
|
||||
desc: String,
|
||||
modifier: Modifier,
|
||||
modifier: Modifier
|
||||
// placeholder: ImageVector
|
||||
) {
|
||||
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
||||
|
@ -22,24 +22,10 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.shabinder.common.database.R
|
||||
import com.shabinder.common.translations.Strings
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
actual fun montserratFont() = FontFamily(
|
||||
Font(R.font.montserrat_light, FontWeight.Light),
|
||||
Font(R.font.montserrat_regular, FontWeight.Normal),
|
||||
Font(R.font.montserrat_medium, FontWeight.Medium),
|
||||
Font(R.font.montserrat_semibold, FontWeight.SemiBold),
|
||||
)
|
||||
|
||||
actual fun pristineFont() = FontFamily(
|
||||
Font(R.font.pristine_script, FontWeight.Bold)
|
||||
)
|
||||
|
||||
@Composable
|
||||
actual fun DownloadImageTick() {
|
||||
Image(
|
||||
@ -49,10 +35,11 @@ actual fun DownloadImageTick() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun DownloadImageError() {
|
||||
actual fun DownloadImageError(modifier: Modifier) {
|
||||
Image(
|
||||
painterResource(R.drawable.ic_error),
|
||||
Strings.downloadError()
|
||||
Strings.downloadError(),
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,141 +0,0 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.OutlinedButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun DonationDialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSnooze: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
isVisible
|
||||
) {
|
||||
|
||||
Dialog(onDismiss) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Gray) // Gray
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
Strings.supportUs(),
|
||||
style = SpotiFlyerTypography.h5,
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorAccent,
|
||||
modifier = Modifier
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(vertical = 4.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.openPlatform("", "https://opencollective.com/spotiflyer/donate")
|
||||
}
|
||||
)
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(OpenCollectiveLogo(), "Open Collective Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Open Collective",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = Strings.worldWideDonations(),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.openPlatform("", "https://www.paypal.com/paypalme/shabinder")
|
||||
}
|
||||
)
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(PaypalLogo(), "Paypal Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Paypal",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = Strings.worldWideDonations(),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp)
|
||||
.clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "RazorPay",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = "${Strings.indianDonations()} (UPI / PayTM / PhonePe / Cards).",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(vertical = 16.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.padding(horizontal = 4.dp).fillMaxWidth()
|
||||
) {
|
||||
OutlinedButton(onClick = onDismiss) {
|
||||
Text(Strings.dismiss())
|
||||
}
|
||||
TextButton(onClick = onSnooze, colors = ButtonDefaults.buttonColors()) {
|
||||
Text(Strings.remindLater())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.shabinder.common.database.R
|
||||
|
||||
actual fun montserratFont() = FontFamily(
|
||||
Font(R.font.montserrat_light, FontWeight.Light),
|
||||
Font(R.font.montserrat_regular, FontWeight.Normal),
|
||||
Font(R.font.montserrat_medium, FontWeight.Medium),
|
||||
Font(R.font.montserrat_semibold, FontWeight.SemiBold),
|
||||
)
|
||||
|
||||
actual fun pristineFont() = FontFamily(
|
||||
Font(R.font.pristine_script, FontWeight.Bold)
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
expect fun Dialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
)
|
@ -8,7 +8,7 @@ import com.shabinder.common.di.Picture
|
||||
expect fun ImageLoad(
|
||||
link: String,
|
||||
loader: suspend () -> Picture,
|
||||
desc: String = "Album Art",
|
||||
modifier: Modifier = Modifier,
|
||||
desc: String,
|
||||
modifier: Modifier
|
||||
// placeholder:Painter = PlaceHolderImage()
|
||||
)
|
||||
|
@ -67,14 +67,7 @@ expect fun RazorPay(): Painter
|
||||
expect fun HeartIcon(): Painter
|
||||
|
||||
@Composable
|
||||
expect fun DownloadImageError()
|
||||
expect fun DownloadImageError(modifier: Modifier)
|
||||
|
||||
@Composable
|
||||
expect fun DownloadImageArrow(modifier: Modifier)
|
||||
|
||||
@Composable
|
||||
expect fun DonationDialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSnooze: () -> Unit
|
||||
)
|
||||
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.ui.graphics.Color
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.graphics.Color
|
@ -1,17 +1,46 @@
|
||||
package com.shabinder.common.uikit.dialogs
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.OutlinedButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.shabinder.common.uikit.DonationDialog
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.Dialog
|
||||
import com.shabinder.common.uikit.OpenCollectiveLogo
|
||||
import com.shabinder.common.uikit.PaypalLogo
|
||||
import com.shabinder.common.uikit.RazorPay
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
|
||||
typealias DonationDialogCallBacks = Triple<openAction,dismissAction,snoozeAction>
|
||||
private typealias openAction = () -> Unit
|
||||
private typealias dismissAction = () -> Unit
|
||||
internal typealias openAction = () -> Unit
|
||||
internal typealias dismissAction = () -> Unit
|
||||
private typealias snoozeAction = () -> Unit
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun DonationDialogComponent(onDismissExtra: () -> Unit): DonationDialogCallBacks {
|
||||
var isDonationDialogVisible by remember { mutableStateOf(false) }
|
||||
@ -31,3 +60,111 @@ fun DonationDialogComponent(onDismissExtra: () -> Unit): DonationDialogCallBacks
|
||||
}
|
||||
return DonationDialogCallBacks(openDonationDialog,dismissDonationDialog,snoozeDonationDialog)
|
||||
}
|
||||
|
||||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun DonationDialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSnooze: () -> Unit
|
||||
) {
|
||||
Dialog(isVisible,onDismiss) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Gray) // Gray
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
Strings.supportUs(),
|
||||
style = SpotiFlyerTypography.h5,
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorAccent,
|
||||
modifier = Modifier
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(vertical = 4.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.openPlatform("", "https://opencollective.com/spotiflyer/donate")
|
||||
}
|
||||
)
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(OpenCollectiveLogo(), "Open Collective Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Open Collective",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = Strings.worldWideDonations(),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.openPlatform("", "https://www.paypal.com/paypalme/shabinder")
|
||||
}
|
||||
)
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(PaypalLogo(), "Paypal Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Paypal",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = Strings.worldWideDonations(),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp)
|
||||
.clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "RazorPay",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = "${Strings.indianDonations()} (UPI / PayTM / PhonePe / Cards).",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(vertical = 16.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.padding(horizontal = 4.dp).fillMaxWidth()
|
||||
) {
|
||||
OutlinedButton(onClick = onDismiss) {
|
||||
Text(Strings.dismiss())
|
||||
}
|
||||
TextButton(onClick = onSnooze, colors = ButtonDefaults.buttonColors()) {
|
||||
Text(Strings.remindLater())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.shabinder.common.uikit.dialogs
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.Dialog
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
|
||||
typealias ErrorInfoDialogCallBacks = Pair<openAction,dismissAction>
|
||||
|
||||
@Composable
|
||||
fun ErrorInfoDialog(error: Throwable): ErrorInfoDialogCallBacks {
|
||||
var isErrorDialogVisible by remember { mutableStateOf(false) }
|
||||
val onDismissDialog = { isErrorDialogVisible = false }
|
||||
val openErrorDialog = { isErrorDialogVisible = true }
|
||||
|
||||
Dialog(isErrorDialogVisible, onDismissDialog) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Color.Gray) // Gray
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
Strings.whatWentWrong(),
|
||||
style = SpotiFlyerTypography.h5,
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorAccent,
|
||||
modifier = Modifier.padding(vertical = 4.dp).fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(Modifier.padding(top = 4.dp))
|
||||
Text(Strings.copyCodeInGithubIssue(), fontWeight = FontWeight.SemiBold)
|
||||
|
||||
SelectionContainer(Modifier.padding(vertical = 8.dp).verticalScroll(rememberScrollState()).weight(1f)) {
|
||||
Text(error.stackTraceToString(), fontWeight = FontWeight.Light)
|
||||
}
|
||||
Row(
|
||||
Modifier.padding(top = 8.dp).fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
TextButton(onClick = onDismissDialog, colors = ButtonDefaults.buttonColors()) {
|
||||
Text(Strings.dismiss())
|
||||
}
|
||||
TextButton(onClick = { methods.value.copyToClipboard(error.stackTraceToString()) }, colors = ButtonDefaults.buttonColors()) {
|
||||
Text(Strings.copyToClipboard())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorInfoDialogCallBacks(openErrorDialog,onDismissDialog)
|
||||
}
|
@ -14,8 +14,9 @@
|
||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.screens
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -27,6 +28,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
@ -37,6 +39,8 @@ import androidx.compose.material.ExtendedFloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -55,7 +59,20 @@ import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.DownloadAllImage
|
||||
import com.shabinder.common.uikit.DownloadImageArrow
|
||||
import com.shabinder.common.uikit.DownloadImageError
|
||||
import com.shabinder.common.uikit.DownloadImageTick
|
||||
import com.shabinder.common.uikit.ImageLoad
|
||||
import com.shabinder.common.uikit.VerticalScrollbar
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.appNameStyle
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
import com.shabinder.common.uikit.configurations.lightGray
|
||||
import com.shabinder.common.uikit.dialogs.DonationDialogComponent
|
||||
import com.shabinder.common.uikit.dialogs.ErrorInfoDialog
|
||||
import com.shabinder.common.uikit.rememberScrollbarAdapter
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@ -133,6 +150,7 @@ fun SpotiFlyerListContent(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun TrackCard(
|
||||
track: TrackDetails,
|
||||
@ -168,7 +186,19 @@ fun TrackCard(
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
is DownloadStatus.Failed -> {
|
||||
DownloadImageError()
|
||||
val (openErrorDialog,dismissErrorDialog) = ErrorInfoDialog((track.downloaded as DownloadStatus.Failed).error)
|
||||
|
||||
Icon(Icons.Rounded.Info,Strings.downloadError(),tint = lightGray,modifier = Modifier.size(42.dp).clickable {
|
||||
openErrorDialog()
|
||||
}.padding(start = 4.dp,end = 12.dp))
|
||||
|
||||
DownloadImageError(
|
||||
Modifier.clickable(
|
||||
onClick = {
|
||||
downloadTrack()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
is DownloadStatus.Downloading -> {
|
||||
CircularProgressIndicator(progress = (track.downloaded as DownloadStatus.Downloading).progress.toFloat() / 100f)
|
@ -14,7 +14,7 @@
|
||||
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.screens
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
@ -84,7 +84,23 @@ import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
|
||||
import com.shabinder.common.models.DownloadRecord
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.GaanaLogo
|
||||
import com.shabinder.common.uikit.GithubLogo
|
||||
import com.shabinder.common.uikit.ImageLoad
|
||||
import com.shabinder.common.uikit.SaavnLogo
|
||||
import com.shabinder.common.uikit.ShareImage
|
||||
import com.shabinder.common.uikit.SpotifyLogo
|
||||
import com.shabinder.common.uikit.VerticalScrollbar
|
||||
import com.shabinder.common.uikit.YoutubeLogo
|
||||
import com.shabinder.common.uikit.YoutubeMusicLogo
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
import com.shabinder.common.uikit.configurations.transparent
|
||||
import com.shabinder.common.uikit.dialogs.DonationDialogComponent
|
||||
import com.shabinder.common.uikit.rememberScrollbarAdapter
|
||||
|
||||
@Composable
|
||||
fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
|
@ -16,7 +16,7 @@
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
package com.shabinder.common.uikit.screens
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
@ -60,8 +60,13 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.root.SpotiFlyerRoot.Child
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.splash.Splash
|
||||
import com.shabinder.common.uikit.splash.SplashState
|
||||
import com.shabinder.common.uikit.SpotiFlyerLogo
|
||||
import com.shabinder.common.uikit.Toast
|
||||
import com.shabinder.common.uikit.ToastDuration
|
||||
import com.shabinder.common.uikit.configurations.appNameStyle
|
||||
import com.shabinder.common.uikit.configurations.colorPrimaryDark
|
||||
import com.shabinder.common.uikit.screens.splash.Splash
|
||||
import com.shabinder.common.uikit.screens.splash.SplashState
|
||||
import com.shabinder.common.uikit.utils.verticalGradientScrim
|
||||
|
||||
// To Not Show Splash Again After Configuration Change in Android
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.uikit.splash
|
||||
package com.shabinder.common.uikit.screens.splash
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@ -38,9 +38,9 @@ import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.HeartIcon
|
||||
import com.shabinder.common.uikit.SpotiFlyerLogo
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorAccent
|
||||
import com.shabinder.common.uikit.colorPrimary
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val SplashWaitTime: Long = 2000
|
@ -0,0 +1,19 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun Dialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(isVisible) {
|
||||
androidx.compose.ui.window.v1.Dialog(onDismiss) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ actual fun ImageLoad(
|
||||
link: String,
|
||||
loader: suspend () -> Picture,
|
||||
desc: String,
|
||||
modifier: Modifier,
|
||||
modifier: Modifier
|
||||
// placeholder: ImageVector
|
||||
) {
|
||||
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
|
||||
|
@ -19,15 +19,10 @@ package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorXmlResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.platform.Font
|
||||
|
||||
@Composable
|
||||
actual fun DownloadImageTick() {
|
||||
@ -37,22 +32,12 @@ actual fun DownloadImageTick() {
|
||||
)
|
||||
}
|
||||
|
||||
actual fun montserratFont() = FontFamily(
|
||||
Font("font/montserrat_light.ttf", FontWeight.Light),
|
||||
Font("font/montserrat_regular.ttf", FontWeight.Normal),
|
||||
Font("font/montserrat_medium.ttf", FontWeight.Medium),
|
||||
Font("font/montserrat_semibold.ttf", FontWeight.SemiBold),
|
||||
)
|
||||
|
||||
actual fun pristineFont() = FontFamily(
|
||||
Font("font/pristine_script.ttf", FontWeight.Bold)
|
||||
)
|
||||
|
||||
@Composable
|
||||
actual fun DownloadImageError() {
|
||||
actual fun DownloadImageError(modifier: Modifier) {
|
||||
Image(
|
||||
vectorXmlResource("drawable/ic_error.xml"),
|
||||
"Can't Download"
|
||||
"Can't Download",
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -1,102 +0,0 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.v1.Dialog
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.translations.Strings
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
actual fun DonationDialog(
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSnooze: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
isVisible
|
||||
) {
|
||||
|
||||
Dialog(onDismiss) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
border = BorderStroke(1.dp, Color.Gray) // Gray
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
Strings.supportUs(),
|
||||
style = SpotiFlyerTypography.h5,
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorAccent,
|
||||
modifier = Modifier
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(vertical = 4.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.openPlatform("", "https://www.paypal.com/paypalme/shabinder")
|
||||
}
|
||||
)
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(PaypalLogo(), "Paypal Logo", tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Paypal",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = Strings.worldWideDonations(),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp)
|
||||
.clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "RazorPay",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = "${Strings.indianDonations()} (UPI / PayTM / PhonePe / Cards).",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.shabinder.common.uikit.configurations
|
||||
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.platform.Font
|
||||
|
||||
|
||||
actual fun montserratFont() = FontFamily(
|
||||
Font("font/montserrat_light.ttf", FontWeight.Light),
|
||||
Font("font/montserrat_regular.ttf", FontWeight.Normal),
|
||||
Font("font/montserrat_medium.ttf", FontWeight.Medium),
|
||||
Font("font/montserrat_semibold.ttf", FontWeight.SemiBold),
|
||||
)
|
||||
|
||||
actual fun pristineFont() = FontFamily(
|
||||
Font("font/pristine_script.ttf", FontWeight.Bold)
|
||||
)
|
@ -31,7 +31,7 @@ val statelyIsoVersion = "1.1.7-a1"
|
||||
i18n4k {
|
||||
inputDirectory = "../../translations"
|
||||
packageName = "com.shabinder.common.translations"
|
||||
// sourceCodeLocales = listOf("en", "de")
|
||||
// sourceCodeLocales = listOf("en", "de", "es", "fr", "id", "pt", "ru", "uk")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
@ -36,6 +36,9 @@ interface Actions {
|
||||
// Share SpotiFlyer App
|
||||
fun shareApp()
|
||||
|
||||
// Copy to Clipboard
|
||||
fun copyToClipboard(text: String)
|
||||
|
||||
// Open / Redirect to another Platform
|
||||
fun openPlatform(packageID: String, platformLink: String)
|
||||
fun writeMp3Tags(trackDetails: TrackDetails)
|
||||
@ -48,6 +51,8 @@ private fun stubActions(): Actions = object : Actions {
|
||||
override fun queryActiveTracks() {}
|
||||
override fun giveDonation() {}
|
||||
override fun shareApp() {}
|
||||
override fun copyToClipboard(text: String) {}
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) {}
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {}
|
||||
|
||||
|
@ -32,8 +32,8 @@ sealed class SpotiFlyerException(override val message: String): Exception(messag
|
||||
val jioSaavnError: Throwable,
|
||||
val ytMusicError: Throwable,
|
||||
override val message: String = "${Strings.noLinkFound()}: $trackName," +
|
||||
" \n JioSaavn Error's StackTrace: ${jioSaavnError.stackTraceToString()} \n " +
|
||||
" \n YtMusic Error's StackTrace: ${ytMusicError.stackTraceToString()} \n "
|
||||
" \n YtMusic Error's StackTrace: ${ytMusicError.stackTraceToString()} \n " +
|
||||
" \n JioSaavn Error's StackTrace: ${jioSaavnError.stackTraceToString()} \n "
|
||||
): SpotiFlyerException(message)
|
||||
|
||||
data class LinkInvalid(
|
||||
|
@ -1,3 +1,5 @@
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.shabinder.common.models.event.coroutines
|
||||
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
@ -101,6 +101,7 @@ class FetchPlatformQueryResult(
|
||||
}
|
||||
Source.YouTube -> {
|
||||
youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull()).flatMapError {
|
||||
logger.e("Yt1sMp3 Failed") { it.message ?: "couldn't fetch link for ${track.videoID} ,trying manual extraction" }
|
||||
youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url?.let { m4aLink ->
|
||||
audioToMp3.convertToMp3(m4aLink)
|
||||
} ?: throw SpotiFlyerException.YoutubeLinkNotFound(track.videoID)
|
||||
@ -130,8 +131,8 @@ class FetchPlatformQueryResult(
|
||||
SuspendableEvent.error(
|
||||
SpotiFlyerException.DownloadLinkFetchFailed(
|
||||
trackName = track.title,
|
||||
jioSaavnError = saavnError,
|
||||
ytMusicError = ytMusicError
|
||||
ytMusicError = ytMusicError,
|
||||
jioSaavnError = saavnError
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ class YoutubeMusic constructor(
|
||||
}
|
||||
// logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||
logger.d(tag) { "Match Found for $trackName - ${!it.isNullOrEmpty()}" }
|
||||
logger.d(tag) { "Match Found for $trackName - ${!it.isNullOrEmpty()} ${it.keys.firstOrNull() ?: ""}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ interface JioSaavnRequests {
|
||||
|
||||
// Skip this Result if No Word is Common in Name
|
||||
if (!hasCommonWord) {
|
||||
logger.i("Saavn Removing Common Word") { result.toString() }
|
||||
// logger.i("Saavn Removing Common Word") { result.toString() }
|
||||
continue
|
||||
}
|
||||
|
||||
@ -264,7 +264,7 @@ interface JioSaavnRequests {
|
||||
}
|
||||
|
||||
if (artistMatchNumber == 0) {
|
||||
logger.i("Artist Match Saavn Removing") { result.toString() }
|
||||
// logger.i("Artist Match Saavn Removing") { result.toString() }
|
||||
continue
|
||||
}
|
||||
val artistMatch: Float = (artistMatchNumber.toFloat() / trackArtists.size) * 100
|
||||
@ -274,11 +274,12 @@ interface JioSaavnRequests {
|
||||
linksWithMatchValue[result.id] = avgMatch
|
||||
}
|
||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap().also {
|
||||
logger.i { "Match Found for $trackName - ${!it.isNullOrEmpty()}" }
|
||||
logger.i(TAG) { "Match Found for $trackName - ${!it.isNullOrEmpty()} ${it.keys.firstOrNull() ?: ""}" }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "Saavn Request"
|
||||
// EndPoints
|
||||
val search_base_url = "${corsApi}https://www.jiosaavn.com/api.php?__call=autocomplete.get&_format=json&_marker=0&cc=in&includeMetaTags=1&query="
|
||||
val song_details_base_url = "${corsApi}https://www.jiosaavn.com/api.php?__call=song.getDetails&cc=in&_marker=0%3F_marker%3D0&_format=json&pids="
|
||||
|
@ -76,6 +76,7 @@ internal class SpotiFlyerRootImpl(
|
||||
) {
|
||||
instanceKeeper.ensureNeverFrozen()
|
||||
methods.value = dependencies.actions.freeze()
|
||||
|
||||
/*Init App Launch & Authenticate Spotify Client*/
|
||||
initAppLaunchAndAuthenticateSpotify(dependencies.fetchQuery::authenticateSpotifyClient)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm")// version "1.4.32"
|
||||
kotlin("jvm")
|
||||
kotlin("plugin.serialization")
|
||||
id("ktlint-setup")
|
||||
id("com.jakewharton.mosaic")
|
||||
|
@ -5,7 +5,7 @@ import com.jakewharton.mosaic.Text
|
||||
import com.jakewharton.mosaic.runMosaic
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
fun main(/*args: Array<String>*/) = runMosaic {
|
||||
fun main() = runMosaic {
|
||||
// TODO https://github.com/JakeWharton/mosaic/issues/3
|
||||
var count by mutableStateOf(0)
|
||||
|
||||
|
@ -37,21 +37,25 @@ import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.PlatformActions
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.uikit.SpotiFlyerColors
|
||||
import com.shabinder.common.uikit.SpotiFlyerRootContent
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorOffWhite
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerColors
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import com.shabinder.common.uikit.screens.SpotiFlyerRootContent
|
||||
import com.shabinder.database.Database
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.piwik.java.tracking.PiwikTracker
|
||||
import utils.trackAsync
|
||||
import utils.trackScreenAsync
|
||||
import java.awt.Desktop
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.Clipboard
|
||||
import java.awt.datatransfer.StringSelection
|
||||
import java.net.URI
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFileChooser.APPROVE_OPTION
|
||||
|
||||
|
||||
private val koin = initKoin(enableNetworkLogs = true).koin
|
||||
private lateinit var showToast: (String)->Unit
|
||||
private val tracker: PiwikTracker by lazy {
|
||||
@ -129,6 +133,11 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
|
||||
}
|
||||
|
||||
override fun shareApp() = openLink("https://github.com/Shabinder/SpotiFlyer")
|
||||
override fun copyToClipboard(text: String) {
|
||||
val data = StringSelection(text)
|
||||
val cb: Clipboard = Toolkit.getDefaultToolkit().systemClipboard
|
||||
cb.setContents(data, data)
|
||||
}
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) = openLink(platformLink)
|
||||
|
||||
|
@ -31,11 +31,3 @@ include(
|
||||
":console-app",
|
||||
":maintenance-tasks"
|
||||
)
|
||||
|
||||
includeBuild("mosaic/mosaic") {
|
||||
dependencySubstitution {
|
||||
substitute(module("com.jakewharton.mosaic:mosaic-gradle-plugin")).with(project(":mosaic-gradle-plugin"))
|
||||
substitute(module("com.jakewharton.mosaic:mosaic-runtime")).with(project(":mosaic-runtime"))
|
||||
substitute(module("com.jakewharton.mosaic:compose-compiler")).with(project(":compose:compiler"))
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Entwickler untestützen
|
||||
donateDescription = Wenn du denkst, dass ich verdiene für meine Arbeit bezahlt zu werden, können sie mich hier unterstützen.If you think I deserve to get paid for my work, you can support me here.
|
||||
share = Teilen
|
||||
shareDescription = Teile diese App mit deiner Familie und deinen Freunden.
|
||||
whatWentWrong = Was schief gelaufen ist...
|
||||
copyToClipboard = In die Zwischenablage kopieren
|
||||
copyCodeInGithubIssue = Kopieren Sie Einfügen unterhalb des Codes, während Sie ein Github-Problem erstellen / Dieses Problem melden, um bessere Hilfe zu erhalten.
|
||||
status = Status
|
||||
analytics = Analyse
|
||||
analyticsDescription = Deine Daten sind Anonym und werden nie mit Drittanbietern geteilt.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Support Developer
|
||||
donateDescription = If you think I deserve to get paid for my work, you can support me here.
|
||||
share = Share
|
||||
shareDescription = Share this app with your friends and family.
|
||||
whatWentWrong = What Went Wrong...
|
||||
copyToClipboard = Copy to Clipboard
|
||||
copyCodeInGithubIssue = Copy Paste Below Code while creating Github Issue / Reporting this issue for better help.
|
||||
status = Status
|
||||
analytics = Analytics
|
||||
analyticsDescription = Your Data is Anonymized and never shared with 3rd party service.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Apoya al desarollador
|
||||
donateDescription = Si crees que merezco una paga por mi trabajo, aquí puedes apoyarme.
|
||||
share = Comparte
|
||||
shareDescription = Comparte esta aplicación con tus familiares y amigos.
|
||||
whatWentWrong = Qué salió mal...
|
||||
copyToClipboard = Copiar al portapapeles
|
||||
copyCodeInGithubIssue = Copie Pegar debajo del código mientras crea Github Issue / Reportando este problema para obtener una mejor ayuda.
|
||||
status = Estatus
|
||||
analytics = Analiticos
|
||||
analyticsDescription = Tus datos son anonimizados y nunca se compartiran con servicios de terceros.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Soutenir le Développeur
|
||||
donateDescription = Si vous pensez que je mérite d'être payé pour mon travail, vous pouvez me soutenir ici.
|
||||
share = Partager
|
||||
shareDescription = Partagez cette application avec vos amis et votre famille.
|
||||
whatWentWrong = Qu'est ce qui ne s'est pas bien passé...
|
||||
copyToClipboard = Copier dans le presse-papier
|
||||
copyCodeInGithubIssue = Copiez et collez le code ci-dessous lors de la création d'un problème Github / Signalement de ce problème pour une meilleure aide.
|
||||
status = Statut
|
||||
analytics = Analyses
|
||||
analyticsDescription = Vos données sont anonymes et ne sont jamais partagées avec des services tiers.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Dukung Developer
|
||||
donateDescription = Jika Anda ingin memberi donasi ke developer apl. ini, Anda bisa donasi disini.
|
||||
share = Bagikan
|
||||
shareDescription = Bagikan aplikasi ini kepada teman / keluarga anda.
|
||||
whatWentWrong = Apa yang salah...
|
||||
copyToClipboard = Menyalin ke clipboard
|
||||
copyCodeInGithubIssue = Salin Tempel Kode Di Bawah Saat membuat Masalah Github / Melaporkan masalah ini untuk bantuan yang lebih baik.
|
||||
status = Status
|
||||
analytics = Analytics
|
||||
analyticsDescription = Data Anda tidak dapat dilihat oleh orang lain dan TIDAK akan dibagikan ke pihak ketiga.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Ajude o Desenvolvedor
|
||||
donateDescription = Se você acha que mereço ser pago pelo meu trabalho, você pode me ajudar aqui.
|
||||
share = Compartilhar
|
||||
shareDescription = Compartilhe este app com seus amigos e família.
|
||||
whatWentWrong = O que deu errado...
|
||||
copyToClipboard = Copiar para área de transferência
|
||||
copyCodeInGithubIssue = Copiar e colar abaixo do código ao criar o problema no Github / relatar esse problema para obter ajuda melhor.
|
||||
status = Status
|
||||
analytics = Estatísticas
|
||||
analyticsDescription = Seus dados serão tornados anônimos e nunca serão compartilhados com serviços de terceiros.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Поддержи разработчика
|
||||
donateDescription = Если считаешь, что я заслуживаю этого, ты можешь поддержать меня здесь.
|
||||
share = Поделиться
|
||||
shareDescription = Расскажи об этом приложении знакомым - поделись полезным инструментом.
|
||||
whatWentWrong = Что пошло не так...
|
||||
copyToClipboard = Скопировать в буфер обмена
|
||||
copyCodeInGithubIssue = Скопируйте вставьте ниже код при создании проблемы Github / Сообщите об этой проблеме для лучшей помощи.
|
||||
status = Статус
|
||||
analytics = Аналитика
|
||||
analyticsDescription = Предоставить анонимные данные для улучшения приложения. Они не будут переданы третьим лицам.
|
||||
|
@ -15,6 +15,9 @@ supportDeveloper = Підтримати розробника
|
||||
donateDescription = Якщо ви вважаєте, що я заслуговую на отримання грошей за свою роботу, ви можете підтримати мене тут.
|
||||
share = Поділитися
|
||||
shareDescription = Поділися цією програмою зі своїми друзями та родиною.
|
||||
whatWentWrong = Що пішло не так...
|
||||
copyToClipboard = Копіювати в буфер обміну
|
||||
copyCodeInGithubIssue = Скопіюйте вставку нижче коду під час створення випуску Github / звітування про цю проблему, щоб отримати кращу допомогу.
|
||||
status = Статус
|
||||
analytics = Аналіз
|
||||
analyticsDescription = Ваші дані анонімні та ніколи не передаються стороннім сервісам.
|
||||
|
@ -71,6 +71,8 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
|
||||
/*TODO("Not yet implemented")*/
|
||||
}
|
||||
|
||||
override fun copyToClipboard(text: String) {}
|
||||
|
||||
override fun setDownloadDirectoryAction() {}
|
||||
|
||||
override fun queryActiveTracks() {}
|
||||
|
Loading…
Reference in New Issue
Block a user