diff --git a/.github/workflows/dependecy-versions.yml b/.github/workflows/dependecy-versions.yml index d62ada37..0ebad47f 100644 --- a/.github/workflows/dependecy-versions.yml +++ b/.github/workflows/dependecy-versions.yml @@ -1,6 +1,9 @@ name: Check Dependency Versions on: + workflow_dispatch: push: + branches: + - compose jobs: dependency-versions: diff --git a/.gitignore b/.gitignore index 6b3cabce..4cd78b89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,23 @@ -TODO - -app/release/* -app/android/release/* -!app/release/.gitkeep -!app/android/release/.gitkeep - -sekret.properties -app/desktop/mongodb-realm -app/shared/sekret/src -extension/background/sekret/src +TODO* +composeApp/sekret.properties +composeApp/sekret/src +composeApp/release/* +composeApp/src/commonMain/moko-resources/assets/* *.so *.dll *.dylib +.idea +captures +xcuserdata/ +Pods/ +*.jks +*yarn.lock + *.iml .gradle +.kotlin /local.properties /.idea .DS_Store @@ -614,4 +616,4 @@ obj/ !/gradle/wrapper/gradle-wrapper.jar -# End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,intellij,intellij+iml,intellij+all,android,androidstudio,maven,gradle,java,kotlin,netbeans,visualstudiocode +# End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,intellij,intellij+iml,intellij+all,android,androidstudio,maven,gradle,java,kotlin,netbeans,visualstudiocode \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/LICENSE b/LICENSE index 541fc112..138e0f87 100644 --- a/LICENSE +++ b/LICENSE @@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/README.md b/README.MD similarity index 99% rename from README.md rename to README.MD index ce9f85ad..e02e122a 100644 --- a/README.md +++ b/README.MD @@ -59,7 +59,7 @@ Search on the internet if you face any problems. Don't ask for help here! ### Browser (Activation Extension) If you can't access [bs.to](https://bs.to) you have to enable DoH (DNS over HTTPS). -Setting this up may differ depending on your browser, however I suggest to use **Cloudflare** as provider. +Setting this up may differ depending on your browser, however I suggest to use **Google** as provider. The extension doesn't need to be updated every version, it just follows the overall versioning scheme. If an update is required/recommended, then there is a hint in the [release notes](https://github.com/DATL4G/Burning-Series/releases/latest). diff --git a/app/android/build.gradle.kts b/app/android/build.gradle.kts deleted file mode 100644 index 26d748d9..00000000 --- a/app/android/build.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.compose) - alias(libs.plugins.crashlytics) - alias(libs.plugins.multiplatform) -} - -val artifact = VersionCatalog.artifactName() - -kotlin { - androidTarget() - - sourceSets { - val androidMain by getting { - dependencies { - implementation(project(":app:shared")) - } - } - } -} - -android { - sourceSets["main"].setRoot("src/androidMain/") - sourceSets["main"].res.srcDirs("src/androidMain/res", "src/commonMain/resources") - sourceSets["main"].assets.srcDirs("src/androidMain/assets", "src/commonMain/assets") - - compileSdk = Configuration.compileSdk - namespace = artifact - - defaultConfig { - applicationId = artifact - minSdk = Configuration.minSdk - targetSdk = Configuration.targetSdk - versionCode = appVersionCode - versionName = appVersion - - multiDexEnabled = true - vectorDrawables.useSupportLibrary = true - } - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } - buildFeatures { - buildConfig = true - } - packaging { - resources.merges.add("META-INF/LICENSE") - resources.merges.add("META-INF/DEPENDENCIES") - resources.pickFirsts.add("**") - resources.pickFirsts.add("**/*") - resources.pickFirsts.add("*") - resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin") - } -} diff --git a/app/android/src/androidMain/jniLibs/arm64-v8a/.gitkeepArm64 b/app/android/src/androidMain/jniLibs/arm64-v8a/.gitkeepArm64 deleted file mode 100644 index e69de29b..00000000 diff --git a/app/android/src/androidMain/jniLibs/armeabi-v7a/.gitkeepArm32 b/app/android/src/androidMain/jniLibs/armeabi-v7a/.gitkeepArm32 deleted file mode 100644 index e69de29b..00000000 diff --git a/app/android/src/androidMain/jniLibs/x86/.gitkeepX32 b/app/android/src/androidMain/jniLibs/x86/.gitkeepX32 deleted file mode 100644 index e69de29b..00000000 diff --git a/app/android/src/androidMain/jniLibs/x86_64/.gitkeepX64 b/app/android/src/androidMain/jniLibs/x86_64/.gitkeepX64 deleted file mode 100644 index e69de29b..00000000 diff --git a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/App.kt b/app/android/src/androidMain/kotlin/dev/datlag/burningseries/App.kt deleted file mode 100644 index 94cbbe72..00000000 --- a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/App.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.datlag.burningseries - -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.multidex.MultiDexApplication -import dev.datlag.burningseries.model.common.systemProperty -import dev.datlag.burningseries.network.state.NetworkStateSaver -import dev.datlag.burningseries.shared.module.NetworkModule -import dev.datlag.burningseries.shared.other.StateSaver -import dev.datlag.sekret.NativeLoader -import io.github.aakira.napier.DebugAntilog -import io.github.aakira.napier.Napier -import kotlinx.coroutines.runBlocking -import org.kodein.di.DI -import org.kodein.di.DIAware -import org.kodein.di.bindSingleton - -class App : MultiDexApplication(), DIAware, DefaultLifecycleObserver { - - private var defaultAllowRestrictedHeaders: String? = null - - override val di: DI by lazy { - DI { - bindSingleton { - applicationContext - } - bindSingleton("APP_VERSION") { - BuildConfig.VERSION_NAME - } - - import(NetworkModule.di) - } - } - - override fun onCreate() { - super.onCreate() - - if (BuildConfig.DEBUG) { - Napier.base(DebugAntilog()) - } - StateSaver.sekretLibraryLoaded = NativeLoader.loadLibrary("sekret") - defaultAllowRestrictedHeaders = systemProperty("sun.net.http.allowRestrictedHeaders", "true") - } - - override fun onDestroy(owner: LifecycleOwner) { - super.onDestroy(owner) - defaultAllowRestrictedHeaders?.ifBlank { null }?.let { - systemProperty("sun.net.http.allowRestrictedHeaders", it) - } - runBlocking { - NetworkStateSaver.firebaseUser?.delete() - } - } -} \ No newline at end of file diff --git a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt b/app/android/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt deleted file mode 100644 index 8286ec38..00000000 --- a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt +++ /dev/null @@ -1,205 +0,0 @@ -package dev.datlag.burningseries - -import android.Manifest -import android.app.PictureInPictureParams -import android.content.res.Configuration -import android.os.Build -import android.os.Bundle -import android.util.Rational -import android.view.KeyEvent -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.* -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.core.view.WindowCompat -import com.arkivanov.decompose.DefaultComponentContext -import com.arkivanov.essenty.backhandler.backHandler -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import com.arkivanov.essenty.lifecycle.essentyLifecycle -import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.shouldShowRationale -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.common.safeCast -import dev.datlag.burningseries.shared.App -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.LocalLifecycleOwner -import dev.datlag.burningseries.shared.other.DomainVerifier -import dev.datlag.burningseries.shared.ui.* -import dev.datlag.burningseries.shared.ui.custom.Permission -import dev.datlag.burningseries.shared.ui.navigation.NavHostComponent -import dev.datlag.kast.Kast -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.runBlocking -import dev.datlag.burningseries.network.state.NetworkStateSaver - -class MainActivity : AppCompatActivity() { - - @OptIn(ExperimentalPermissionsApi::class) - override fun onCreate(savedInstanceState: Bundle?) { - if (savedInstanceState != null) { - this.setTheme(R.style.AppTheme) - } else { - installSplashScreen() - } - super.onCreate(savedInstanceState) - - WindowCompat.setDecorFitsSystemWindows(window, false) - enableEdgeToEdge() - - val di = applicationContext.safeCast()?.di ?: (application as App).di - - val lifecycleOwner = object : LifecycleOwner { - override val lifecycle: Lifecycle = essentyLifecycle() - } - - val shortcutIntent = BSUtil.getIntentDataUrl(intent?.data?.toString()) - val root = NavHostComponent( - componentContext = DefaultComponentContext( - lifecycle = lifecycleOwner.lifecycle, - backHandler = backHandler() - ), - di = di, - shortcutIntent = shortcutIntent - ) - - Kast.setup(this) - SmallIcon = R.drawable.ic_launcher_foreground - DomainVerifier.verify(this) - - setContent { - CompositionLocalProvider( - LocalLifecycleOwner provides lifecycleOwner - ) { - App(di) { - root.render() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - var showDialog by remember { mutableStateOf(true) } - var showDialogAgain by remember { mutableStateOf(true) } - - Permission( - permission = Manifest.permission.POST_NOTIFICATIONS, - onGranted = { - NotificationPermission = true - }, - onShowInfo = { - if (showDialog && showDialogAgain) { - val text = if (it.status.shouldShowRationale) { - SharedRes.strings.permission_notification_rational - } else { - SharedRes.strings.permission_notification - } - - NotificationDialog( - text = stringResource(text), - onConfirm = { - it.launchPermissionRequest() - }, - onDismiss = { force -> - showDialog = false - showDialogAgain = !force - } - ) - } - }, - onDeniedForever = { - showDialog = false - showDialogAgain = false - } - ) - } else { - NotificationPermission = true - } - } - } - } - } - - override fun onDestroy() { - super.onDestroy() - - Kast.dispose() - DomainVerifier.verify(this) - runBlocking { - NetworkStateSaver.firebaseUser?.delete() - } - } - - override fun onStart() { - super.onStart() - - DomainVerifier.verify(this) - } - - override fun onResume() { - super.onResume() - - DomainVerifier.verify(this) - } - - override fun onPause() { - super.onPause() - - DomainVerifier.verify(this) - } - - override fun onStop() { - super.onStop() - - DomainVerifier.verify(this) - } - - override fun onRestart() { - super.onRestart() - - DomainVerifier.verify(this) - } - - override fun dispatchKeyEvent(event: KeyEvent): Boolean { - return (KeyEventDispatcher.invoke(event) ?: false) || super.dispatchKeyEvent(event) - } - - override fun onUserLeaveHint() { - if (PIPEnabled) { - enterPIPMode() - } else { - super.onUserLeaveHint() - } - } - - override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { - super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - - PIPModeListener.invoke(isInPictureInPictureMode) - } - - private fun enterPIPMode() { - if (!PIPEnabled) { - return - } - - val builder = PictureInPictureParams.Builder() - .setAspectRatio(Rational(16, 9)) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - builder.setAutoEnterEnabled(true) - } - - val actions = PIPActions.invoke() - if (!actions.isNullOrEmpty()) { - builder.setActions(actions) - } - - this.enterPictureInPictureMode(builder.build()) - } - - override fun enterPictureInPictureMode(params: PictureInPictureParams): Boolean { - return if (!PIPEnabled) { - false - } else { - super.enterPictureInPictureMode(params) - } - } -} \ No newline at end of file diff --git a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/NotificationDialog.kt b/app/android/src/androidMain/kotlin/dev/datlag/burningseries/NotificationDialog.kt deleted file mode 100644 index ff1c262e..00000000 --- a/app/android/src/androidMain/kotlin/dev/datlag/burningseries/NotificationDialog.kt +++ /dev/null @@ -1,55 +0,0 @@ -package dev.datlag.burningseries - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun NotificationDialog(text: String, onConfirm: () -> Unit, onDismiss: (Boolean) -> Unit) { - AlertDialog( - onDismissRequest = { - onDismiss(false) - }, - icon = { - Icon( - imageVector = Icons.Default.Notifications, - contentDescription = stringResource(SharedRes.strings.notifications) - ) - }, - title = { - Text( - text = stringResource(SharedRes.strings.notifications), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = text) - }, - confirmButton = { - Button( - onClick = { - onConfirm() - onDismiss(false) - } - ) { - Text(text = stringResource(SharedRes.strings.grant)) - } - }, - dismissButton = { - Button( - onClick = { - onDismiss(true) - } - ) { - Text(text = stringResource(SharedRes.strings.deny)) - } - } - ) -} \ No newline at end of file diff --git a/app/android/src/androidMain/res/drawable/ic_launcher_foreground.xml b/app/android/src/androidMain/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index a766ee2c..00000000 --- a/app/android/src/androidMain/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/app/android/src/androidMain/res/mipmap-xhdpi/tv_banner.png b/app/android/src/androidMain/res/mipmap-xhdpi/tv_banner.png deleted file mode 100644 index 10c3528d..00000000 Binary files a/app/android/src/androidMain/res/mipmap-xhdpi/tv_banner.png and /dev/null differ diff --git a/app/desktop/Burning-Series.desktop b/app/desktop/Burning-Series.desktop deleted file mode 100644 index cd2201a5..00000000 --- a/app/desktop/Burning-Series.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Burning-Series -Comment=Watch any series from Burning-Series using this app. This is an unofficial Burning-Series App -Exec=Burning-Series -Icon=Burning-Series -Terminal=false -Categories=Video;AudioVideo; \ No newline at end of file diff --git a/app/desktop/build.gradle.kts b/app/desktop/build.gradle.kts deleted file mode 100644 index 18a95478..00000000 --- a/app/desktop/build.gradle.kts +++ /dev/null @@ -1,154 +0,0 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat - -plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.compose) - alias(libs.plugins.osdetector) -} - -val artifact = VersionCatalog.artifactName() - -group = artifact -version = appVersion - -kotlin { - jvm() - - sourceSets { - val jvmMain by getting { - dependencies { - implementation(project(":app:shared")) - } - } - } -} - -compose { - desktop { - application { - mainClass = "$artifact.MainKt" - - jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") - jvmArgs("--add-opens", "java.desktop/sun.awt=ALL-UNNAMED") - jvmArgs("--add-opens", "java.desktop/sun.java2d=ALL-UNNAMED") - jvmArgs("--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED") - when (getHost()) { - Host.Linux -> { - jvmArgs("--add-opens", "java.desktop/sun.awt.X11=ALL-UNNAMED") - jvmArgs("--add-opens", "java.desktop/sun.awt.wl=ALL-UNNAMED") - } - Host.MAC -> { - jvmArgs("--add-opens", "java.desktop/sun.lwawt=ALL-UNNAMED") - jvmArgs("--add-opens", "java.desktop/sun.lwawt.macosx=ALL-UNNAMED") - } - else -> { } - } - - nativeDistributions { - packageName = "Burning-Series" - packageVersion = appVersion - outputBaseDir.set(rootProject.layout.buildDirectory.asFile.get().resolve("release")) - description = "Watch any series from Burning-Series using this (unofficial) app." - copyright = "© 2020 Jeff Retz (DatLag). All rights reserved." - licenseFile.set(rootProject.file("LICENSE")) - - outputBaseDir.set(rootProject.layout.buildDirectory.asFile.get().resolve("release")) - appResourcesRootDir.set(project.layout.projectDirectory.dir("resources")) - - when (getHost()) { - Host.Linux -> targetFormats( - TargetFormat.AppImage, TargetFormat.Deb, TargetFormat.Rpm - ) - Host.MAC -> targetFormats( - TargetFormat.Dmg - ) - Host.Windows -> targetFormats( - TargetFormat.Exe, TargetFormat.Msi - ) - } - - linux { - iconFile.set(File(rootProject.project("app").project("shared").projectDir, "src/commonMain/resources/MR/assets/png/launcher_128.png")) - rpmLicenseType = "GPL-3.0" - debMaintainer = "Jeff Retz (DatLag)" - appCategory = "Video" - } - windows { - iconFile.set(File(rootProject.project("app").project("shared").projectDir, "src/commonMain/resources/MR/assets/ico/launcher_128.ico")) - upgradeUuid = "3487d337-1ef5-4e01-87cb-d1ede6e10752" - } - macOS { - iconFile.set(File(rootProject.project("app").project("shared").projectDir, "src/commonMain/resources/MR/assets/icns/launcher.icns")) - } - - modules( - "javax.xml", - "javax.xml.datatype", - "javax.xml.namespace", - "javax.xml.parsers", - "javax.xml.stream", - "javax.xml.stream.events", - "javax.xml.stream.util", - "javax.xml.transform", - "javax.xml.transform.dom", - "javax.xml.transform.sax", - "javax.xml.transform.stax", - "javax.xml.transform.stream", - "javax.xml.validation", - "javax.xml.xpath", - - "org.w3c.dom", - "org.w3c.dom.bootstrap", - "org.w3c.dom.css", - "org.w3c.dom.events", - "org.w3c.dom.html", - "org.w3c.dom.ls", - "org.w3c.dom.ranges", - "org.w3c.dom.stylesheets", - "org.w3c.dom.traversal", - "org.w3c.dom.views", - "org.w3c.dom.xpath", - - "org.xml.sax", - "org.xml.sax.ext", - "org.xml.sax.helpers", - - "java.base", - "java.compiler", - "java.instrument", - "java.management", - "java.naming", - "java.sql", - "jdk.unsupported", - "jdk.xml.dom" - ) - includeAllModules = true - } - } - } -} - -fun getHost(): Host { - return when (osdetector.os) { - "linux" -> Host.Linux - "osx" -> Host.MAC - "windows" -> Host.Windows - else -> { - val hostOs = System.getProperty("os.name") - val isMingwX64 = hostOs.startsWith("Windows") - - when { - hostOs == "Linux" -> Host.Linux - hostOs == "Mac OS X" -> Host.MAC - isMingwX64 -> Host.Windows - else -> throw IllegalStateException("Unknown OS: ${osdetector.classifier}") - } - } - } -} - -enum class Host(val label: String) { - Linux("linux"), - Windows("win"), - MAC("mac"); -} \ No newline at end of file diff --git a/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/InitCEF.kt b/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/InitCEF.kt deleted file mode 100644 index c0a7b5be..00000000 --- a/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/InitCEF.kt +++ /dev/null @@ -1,62 +0,0 @@ -package dev.datlag.burningseries - -import androidx.compose.runtime.* -import dev.datlag.burningseries.shared.AppIO -import dev.datlag.burningseries.shared.common.LocalRestartRequired -import dev.datlag.burningseries.shared.common.withIOContext -import dev.datlag.burningseries.shared.other.CEFState -import dev.datlag.burningseries.shared.other.LocalCEFInitialization -import dev.datlag.burningseries.shared.window.ApplicationDisposer -import dev.datlag.kcef.KCEF -import dev.datlag.kcef.KCEFBuilder -import java.io.File - -@Composable -fun InitCEF(content: @Composable () -> Unit) { - val restartRequiredInitial = LocalRestartRequired.current - var restartRequired by remember { mutableStateOf(restartRequiredInitial) } - val cefState = remember { mutableStateOf(CEFState.LOCATING) } - - LaunchedEffect(ApplicationDisposer.current) { - withIOContext { - KCEF.init( - builder = { - installDir(File(AppIO.getWriteableExecutableFolder(), "kcef-bundle")) - progress { - onLocating { - cefState.value = CEFState.LOCATING - } - onDownloading { - cefState.value = CEFState.Downloading(it) - } - onExtracting { - cefState.value = CEFState.EXTRACTING - } - onInstall { - cefState.value = CEFState.INSTALLING - } - onInitializing { - cefState.value = CEFState.INITIALIZING - } - onInitialized { - cefState.value = CEFState.INITIALIZED - } - } - settings { - logSeverity = KCEFBuilder.Settings.LogSeverity.Disable - } - }, - onRestartRequired = { - restartRequired = true - } - ) - } - } - - CompositionLocalProvider( - LocalRestartRequired provides restartRequired, - LocalCEFInitialization provides cefState - ) { - content() - } -} \ No newline at end of file diff --git a/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt b/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt deleted file mode 100644 index 277f1526..00000000 --- a/app/desktop/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt +++ /dev/null @@ -1,154 +0,0 @@ -package dev.datlag.burningseries - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBackIosNew -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.window.WindowState -import com.arkivanov.decompose.DefaultComponentContext -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.lifecycle.LifecycleController -import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.PredictiveBackGestureIcon -import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.PredictiveBackGestureOverlay -import com.arkivanov.essenty.backhandler.BackDispatcher -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import com.arkivanov.essenty.lifecycle.LifecycleRegistry -import com.google.firebase.FirebasePlatform -import dev.datlag.burningseries.model.common.systemProperty -import dev.datlag.burningseries.network.state.NetworkStateSaver -import dev.datlag.burningseries.shared.App -import dev.datlag.burningseries.shared.AppIO -import dev.datlag.burningseries.shared.LocalWindow -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.LocalLifecycleOwner -import dev.datlag.burningseries.shared.module.NetworkModule -import dev.datlag.burningseries.shared.other.StateSaver -import dev.datlag.burningseries.shared.ui.navigation.NavHostComponent -import dev.datlag.burningseries.shared.window.disposableSingleWindowApplication -import dev.datlag.sekret.NativeLoader -import dev.icerock.moko.resources.desc.Resource -import dev.icerock.moko.resources.desc.StringDesc -import io.github.aakira.napier.DebugAntilog -import io.github.aakira.napier.Napier -import kotlinx.coroutines.runBlocking -import org.kodein.di.DI -import org.kodein.di.bindSingleton -import java.io.File - -fun main(vararg args: String) { - StateSaver.sekretLibraryLoaded = NativeLoader.loadLibrary("sekret", systemProperty("compose.application.resources.dir")?.let { File(it) }) - - FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() { - val storage = mutableMapOf() - override fun store(key: String, value: String) = storage.set(key, value) - override fun retrieve(key: String) = storage[key] - override fun clear(key: String) { storage.remove(key) } - override fun log(msg: String) = println(msg) - }) - - val di = DI { - systemProperty("jpackage.app-version")?.let { - bindSingleton("APP_VERSION") { - it - } - } - - import(NetworkModule.di) - } - - Runtime.getRuntime().addShutdownHook(Thread { - runBlocking { - NetworkStateSaver.firebaseUser?.delete() - } - }) - - runWindow(di) - - runBlocking { - NetworkStateSaver.firebaseUser?.delete() - } -} - -@OptIn(ExperimentalDecomposeApi::class) -private fun runWindow(di: DI) { - val appTitle = StringDesc.Resource(SharedRes.strings.app_name).localized() - AppIO.applyTitle(appTitle) - Napier.base(DebugAntilog()) - - val windowState = WindowState() - val lifecycle = LifecycleRegistry() - val lifecycleOwner = object : LifecycleOwner { - override val lifecycle: Lifecycle = lifecycle - } - val backDispatcher = BackDispatcher() - val root = NavHostComponent( - componentContext = DefaultComponentContext( - lifecycle, - backHandler = backDispatcher - ), - di = di - ) - - disposableSingleWindowApplication( - state = windowState, - title = appTitle, - onKeyEvent = { - false - }, - exitProcessOnExit = true - ) { - LifecycleController(lifecycle, windowState) - - AppIO.loadAppIcon( - this.window, - rememberCoroutineScope(), - SharedRes.assets.icns.launcher, - SharedRes.assets.ico.launcher_128, - SharedRes.assets.ico.launcher_96, - SharedRes.assets.ico.launcher_64, - SharedRes.assets.ico.launcher_48, - SharedRes.assets.ico.launcher_32, - SharedRes.assets.ico.launcher_16, - SharedRes.assets.png.launcher_128, - SharedRes.assets.png.launcher_96, - SharedRes.assets.png.launcher_64, - SharedRes.assets.png.launcher_48, - SharedRes.assets.png.launcher_32, - SharedRes.assets.png.launcher_16, - SharedRes.assets.svg.launcher_128, - SharedRes.assets.svg.launcher_96, - SharedRes.assets.svg.launcher_64, - SharedRes.assets.svg.launcher_48, - SharedRes.assets.svg.launcher_32, - SharedRes.assets.svg.launcher_16, - ) - - InitCEF { - CompositionLocalProvider( - LocalLifecycleOwner provides lifecycleOwner, - LocalWindow provides this.window - ) { - App(di) { - PredictiveBackGestureOverlay( - backDispatcher = backDispatcher, - backIcon = { progress, _ -> - PredictiveBackGestureIcon( - imageVector = Icons.Default.ArrowBackIosNew, - progress = progress, - iconTintColor = MaterialTheme.colorScheme.onSecondaryContainer, - backgroundColor = MaterialTheme.colorScheme.secondaryContainer - ) - }, - modifier = Modifier.fillMaxSize() - ) { - root.render() - } - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/build.gradle.kts b/app/shared/build.gradle.kts deleted file mode 100644 index ba6f90d8..00000000 --- a/app/shared/build.gradle.kts +++ /dev/null @@ -1,284 +0,0 @@ -import com.mikepenz.aboutlibraries.plugin.DuplicateMode -import com.mikepenz.aboutlibraries.plugin.DuplicateRule - -plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.android.library) - alias(libs.plugins.compose) - alias(libs.plugins.aboutlibraries) - id("kotlin-parcelize") apply false - alias(libs.plugins.serialization) - alias(libs.plugins.moko.resources) - alias(libs.plugins.ktorfit) - alias(libs.plugins.sekret) -} - -val artifact = VersionCatalog.artifactName("shared") - -group = artifact -version = appVersion - -sekret { - packageName = artifact - propertiesFile = rootDir.canonicalPath -} - -kotlin { - androidTarget() - jvm("desktop") - - jvmToolchain(CompileOptions.jvmTargetVersion) - - applyDefaultHierarchyTemplate() - - sourceSets { - val commonMain by getting { - dependencies { - api(compose.runtime) - api(compose.foundation) - api(compose.material3) - api(compose.materialIconsExtended) - api(compose.ui) - api(compose.animation) - api(compose.animationGraphics) - - api(libs.stdlib) - - api(libs.decompose) - api(libs.decompose.compose) - api(libs.coroutines) - api(libs.kodein) - api(libs.kodein.compose) - - implementation(libs.aboutlibraries) - - api(libs.moko.resources.compose) - api(libs.blurhash) - api(libs.kmpalette) - api(libs.kolor) - - api(libs.windowsize.multiplatform) - - implementation(libs.haze) - implementation(libs.haze.materials) - implementation(libs.kache) - - api(libs.ktor) - api(libs.ktor.content.negotiation) - api(libs.ktor.serialization.json) - - api("dev.datlag.kast:kast:0.1.8-SNAPSHOT") - api(libs.coil) - api(libs.coil.network) - api(libs.coil.compose) - - api(project(":model")) - api(project(":network")) - api(project(":database")) - api(project("sekret")) - } - } - val androidMain by getting { - dependsOn(commonMain) - apply(plugin = "kotlin-parcelize") - - dependencies { - api(libs.activity) - api(libs.activity.compose) - api(libs.android) - api(libs.appcompat) - api(libs.coroutines.android) - api(libs.material) - api(libs.multidex) - api(libs.splashscreen) - api(libs.ktor.jvm) - api(libs.media3) - api(libs.media3.dash) - api(libs.media3.hls) - api(libs.media3.rtsp) - api(libs.media3.session) - api(libs.media3.smooth) - api(libs.media3.ui) - api(libs.media3.cast) - api(libs.accompanist.uicontroller) - api(libs.webview.android) - api(libs.permission) - api(libs.nanoid) - - api(libs.firebase.crashlytics) - api(libs.firebase.android.crashlytics) - - api(libs.coil.gif) - api(libs.coil.svg) - - api(libs.okhttp.doh) - } - } - val desktopMain by getting { - dependsOn(commonMain) - - dependencies { - api(compose.desktop.currentOs) - api(libs.coroutines.swing) - api(libs.context.menu) - api(libs.ktor.jvm) - api(libs.appdirs) - api(libs.vlcj) - api(libs.webview.desktop) - - api(libs.okhttp.doh) - } - } - } -} - -android { - sourceSets["main"].setRoot("src/androidMain/") - sourceSets["main"].res.srcDirs("src/androidMain/res", "src/commonMain/resources") - sourceSets["main"].assets.srcDirs("src/androidMain/assets", "src/commonMain/assets") - - compileSdk = Configuration.compileSdk - namespace = artifact - - defaultConfig { - minSdk = Configuration.minSdk - } - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } - buildFeatures { - buildConfig = true - } - packaging { - resources.merges.add("META-INF/LICENSE") - resources.merges.add("META-INF/DEPENDENCIES") - resources.pickFirsts.add("**") - resources.pickFirsts.add("**/*") - resources.pickFirsts.add("*") - resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin") - } -} - -multiplatformResources { - multiplatformResourcesPackage = artifact - multiplatformResourcesClassName = "SharedRes" -} - -aboutLibraries { - includePlatform = true - duplicationMode = DuplicateMode.MERGE - duplicationRule = DuplicateRule.GROUP - excludeFields = arrayOf("generated") -} - -val createNativeLib = tasks.create("createNativeLib") { - dependsOn(tasks.generateSekret, "sekret:assemble") - - fun getBinPath(target: String): String? { - val buildDir = project("sekret").layout.buildDirectory.asFile.get() - return if (File(buildDir, "bin/$target/releaseShared").exists()) { - File(buildDir, "bin/$target/releaseShared").canonicalPath - } else if (File(buildDir, "bin/$target/debugShared").exists()) { - File(buildDir, "bin/$target/debugShared").canonicalPath - } else { - null - } - } - - doLast { - val androidArm32 = getBinPath("androidNativeArm32") - val androidArm64 = getBinPath("androidNativeArm64") - val androidX64 = getBinPath("androidNativeX64") - val androidX86 = getBinPath("androidNativeX86") - - if (androidArm32 != null) { - copy { - from(androidArm32) { - exclude("*.h") - exclude("*.def") - } - into("../android/src/androidMain/jniLibs/armeabi-v7a") - } - } - if (androidArm64 != null) { - copy { - from(androidArm64) { - exclude("*.h") - exclude("*.def") - } - into("../android/src/androidMain/jniLibs/arm64-v8a") - } - } - if (androidX64 != null) { - copy { - from(androidX64) { - exclude("*.h") - exclude("*.def") - } - into("../android/src/androidMain/jniLibs/x86_64") - } - } - if (androidX86 != null) { - copy { - from(androidX86) { - exclude("*.h") - exclude("*.def") - } - into("../android/src/androidMain/jniLibs/x86") - } - } - - val linuxArm64 = getBinPath("linuxArm64") - val linuxX64 = getBinPath("linuxX64") - val mingwX64 = getBinPath("mingwX64") - val macosArm64 = getBinPath("macosArm64") - val macosX64 = getBinPath("macosX64") - - if (linuxArm64 != null) { - copy { - from(linuxArm64) { - exclude("*.h") - exclude("*.def") - } - into("../desktop/resources/linux-arm64") - } - } - if (linuxX64 != null) { - copy { - from(linuxX64) { - exclude("*.h") - exclude("*.def") - } - into("../desktop/resources/linux-x64") - } - } - if (mingwX64 != null) { - copy { - from(mingwX64) { - exclude("*.h") - exclude("*.def") - } - into("../desktop/resources/windows") - } - } - if (macosArm64 != null) { - copy { - from(macosArm64) { - exclude("*.h") - exclude("*.def") - } - into("../desktop/resources/macos-arm64") - } - } - if (macosX64 != null) { - copy { - from(macosX64) { - exclude("*.h") - exclude("*.def") - } - into("../desktop/resources/macos-x64") - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/AndroidManifest.xml b/app/shared/src/androidMain/AndroidManifest.xml deleted file mode 100644 index ffb7f703..00000000 --- a/app/shared/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/App.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/App.android.kt deleted file mode 100644 index b4813bae..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/App.android.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.datlag.burningseries.shared - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import dev.datlag.burningseries.shared.common.isTv - -@Composable -actual fun SystemProvider(content: @Composable () -> Unit) { - content() -} - -@Composable -actual fun rememberIsTv(): Boolean { - val context = LocalContext.current - return remember { context.isTv() } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/PackageName.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/PackageName.android.kt deleted file mode 100644 index f3ce07e9..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/PackageName.android.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.datlag.burningseries.shared - -actual fun getPackageName(): String { - return BuildConfig.LIBRARY_PACKAGE_NAME -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/ExtendContext.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/ExtendContext.kt deleted file mode 100644 index 66af1479..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/ExtendContext.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.content.pm.PackageManager -import android.view.Window - -tailrec fun Context.findWindow(): Window? = when (this) { - is Activity -> window - is ContextWrapper -> baseContext.findWindow() - else -> null -} - -tailrec fun Context.findActivity(): Activity? = when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null -} - -@Suppress("DEPRECATION") -fun Context.isTv(): Boolean { - val packageManager = this.packageManager ?: this.applicationContext.packageManager - return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION) || packageManager.hasSystemFeature( - PackageManager.FEATURE_LEANBACK - ) || packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY) -} - -fun Context.isPackageInstalled(packageName: String): Boolean { - return try { - packageManager.getPackageInfo(packageName, 0) - true - } catch (e: PackageManager.NameNotFoundException) { - false - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.android.kt deleted file mode 100644 index 840fdae2..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.android.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import android.content.Context -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.asImageBitmap -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation -import com.arkivanov.decompose.extensions.compose.stack.animation.fade -import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation -import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation -import com.arkivanov.essenty.backhandler.BackHandler -import com.vanniktech.blurhash.BlurHash -import org.kodein.di.DI -import org.kodein.di.instance -import com.vanniktech.blurhash.BlurHash.decode as defaultDecode - -@OptIn(ExperimentalFoundationApi::class) -actual fun Modifier.onClick( - enabled: Boolean, - onDoubleClick: (() -> Unit)?, - onLongClick: (() -> Unit)?, - onClick: () -> Unit -): Modifier { - return this.combinedClickable( - enabled = enabled, - onDoubleClick = onDoubleClick, - onLongClick = onLongClick, - onClick = onClick - ) -} - -@OptIn(ExperimentalDecomposeApi::class) -actual fun backAnimation( - backHandler: BackHandler, - onBack: () -> Unit -): StackAnimation = predictiveBackAnimation( - backHandler = backHandler, - fallbackAnimation = stackAnimation(fade()), - onBack = onBack -) - -actual fun BlurHash.decode(hash: String, width: Int, height: Int): ImageBitmap? { - val bitmap = defaultDecode(hash, width, height) - return bitmap?.asImageBitmap() -} - -actual fun String.openInBrowser(di: DI) { - val context by di.instance() - - this.openInBrowser(context) -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.android.kt deleted file mode 100644 index 0318c58e..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.android.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainCoroutineDispatcher - -actual val Dispatchers.DeviceMain: MainCoroutineDispatcher - get() = Main - -actual val Dispatchers.DeviceIO: CoroutineDispatcher - get() = IO - -actual val Dispatchers.DeviceDefault : CoroutineDispatcher - get() = Default \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt deleted file mode 100644 index 1062fb1f..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt +++ /dev/null @@ -1,22 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import android.content.Context -import android.content.Intent -import androidx.core.content.ContextCompat -import androidx.core.net.toUri -import dev.datlag.burningseries.model.common.scopeCatching - -fun String.openInBrowser(context: Context) { - val browserIntent = Intent(Intent.ACTION_VIEW, this.toUri()) - - if (scopeCatching { - ContextCompat.startActivity(context, browserIntent, null) - }.isSuccess) { - return - } - - val newIntent = browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - scopeCatching { - ContextCompat.startActivity(context, newIntent, null) - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.android.kt deleted file mode 100644 index ab685188..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.android.kt +++ /dev/null @@ -1,125 +0,0 @@ -package dev.datlag.burningseries.shared.module - -import android.content.Context -import coil3.ImageLoader -import coil3.annotation.ExperimentalCoilApi -import coil3.disk.DiskCache -import coil3.gif.GifDecoder -import coil3.memory.MemoryCache -import coil3.network.ktor.KtorNetworkFetcherFactory -import coil3.request.allowHardware -import coil3.request.crossfade -import coil3.svg.SvgDecoder -import dev.datlag.burningseries.database.DriverFactory -import dev.datlag.burningseries.shared.Sekret -import dev.datlag.burningseries.shared.getPackageName -import dev.datlag.burningseries.shared.other.StateSaver -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseOptions -import dev.gitlive.firebase.initialize -import io.ktor.client.* -import io.ktor.client.engine.okhttp.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.realm.kotlin.mongodb.AppConfiguration -import kotlinx.serialization.json.Json -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.dnsoverhttps.DnsOverHttps -import okio.FileSystem -import org.kodein.di.DI -import org.kodein.di.bindEagerSingleton -import org.kodein.di.bindSingleton -import org.kodein.di.instance -import java.net.InetAddress -import java.util.concurrent.TimeUnit - -actual object PlatformModule { - - private const val NAME = "PlatformModuleAndroid" - - @OptIn(ExperimentalCoilApi::class) - actual val di: DI.Module = DI.Module(NAME) { - bindSingleton { - Json { - ignoreUnknownKeys = true - isLenient = true - } - } - bindSingleton { - OkHttpClient.Builder() - .followRedirects(true) - .followSslRedirects(true) - .connectTimeout(3, TimeUnit.MINUTES) - .readTimeout(3, TimeUnit.MINUTES) - .writeTimeout(3, TimeUnit.MINUTES) - .build() - } - bindSingleton { - DnsOverHttps.Builder() - .client(instance()) - .url("https://dns.google/dns-query".toHttpUrl()) - .bootstrapDnsHosts(InetAddress.getByName("8.8.4.4"), InetAddress.getByName("8.8.8.8")) - .build() - } - bindSingleton { - HttpClient(OkHttp) { - engine { - config { - followRedirects(true) - connectTimeout(3, TimeUnit.MINUTES) - readTimeout(3, TimeUnit.MINUTES) - writeTimeout(3, TimeUnit.MINUTES) - dns(instance()) - } - } - install(ContentNegotiation) { - json(instance(), ContentType.Application.Json) - json(instance(), ContentType.Text.Plain) - } - } - } - bindSingleton { - DriverFactory(instance()) - } - if (StateSaver.sekretLibraryLoaded) { - bindEagerSingleton { - AppConfiguration.create(Sekret().mongoApplication(getPackageName())!!) - } - bindEagerSingleton { - Firebase.initialize( - context = instance(), - options = FirebaseOptions( - applicationId = Sekret().firebaseApplication(getPackageName())!!, - apiKey = Sekret().firebaseApiKey(getPackageName())!!, - projectId = Sekret().firebaseProject(getPackageName()) - ) - ) - } - } - bindSingleton { - ImageLoader.Builder(instance()) - .components { - add(KtorNetworkFetcherFactory(instance())) - add(GifDecoder.Factory()) - add(SvgDecoder.Factory()) - } - .memoryCache { - MemoryCache.Builder() - .maxSizePercent(instance()) - .build() - } - .diskCache { - DiskCache.Builder() - .directory(FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "image_cache") - .maxSizeBytes(512L * 1024 * 1024) // 512MB - .build() - } - .allowHardware(false) - .crossfade(true) - .build() - } - } - -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.android.kt deleted file mode 100644 index 4f0199c7..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.android.kt +++ /dev/null @@ -1,32 +0,0 @@ -package dev.datlag.burningseries.shared.other - -import dev.datlag.burningseries.model.common.name -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.crashlytics.crashlytics - -actual object Crashlytics { - actual fun customKey(key: String, value: String) { - Firebase.crashlytics.setCustomKey(key, value) - } - - actual fun customKey(key: String, value: Boolean) { - Firebase.crashlytics.setCustomKey(key, value) - } - actual fun customKey(key: String, value: Int) { - Firebase.crashlytics.setCustomKey(key, value) - } - actual fun customKey(key: String, value: Long) { - Firebase.crashlytics.setCustomKey(key, value) - } - actual fun customKey(key: String, value: Float) { - Firebase.crashlytics.setCustomKey(key, value) - } - actual fun customKey(key: String, value: Double) { - Firebase.crashlytics.setCustomKey(key, value) - } - - actual fun screen(value: Component) { - customKey("Screen", value::class.name) - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/Events.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/Events.kt deleted file mode 100644 index 77bf22d6..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/Events.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui - -import android.app.RemoteAction -import android.view.KeyEvent - -var SmallIcon: Int = 0 -var NotificationPermission: Boolean = false - -var KeyEventDispatcher: (event: KeyEvent?) -> Boolean? = { null } -var PIPEnabled: Boolean = false -var PIPModeListener: (Boolean) -> Unit = { } -var PIPActions: () -> ArrayList? = { null } \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Permission.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Permission.kt deleted file mode 100644 index 7f932c48..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Permission.kt +++ /dev/null @@ -1,37 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.runtime.* -import com.google.accompanist.permissions.* - -@OptIn(ExperimentalPermissionsApi::class) -@Composable -fun Permission( - permission: String, - onGranted: @Composable () -> Unit, - onShowInfo: @Composable (PermissionState) -> Unit, - onDeniedForever: @Composable (PermissionState) -> Unit -) { - var callbackResult by remember { mutableStateOf(null) } - val permissionState = rememberPermissionState( - permission - ) { granted -> - callbackResult = granted - } - - if (permissionState.status.isGranted) { - onGranted() - } else { - if (callbackResult == true) { - SideEffect { - // Re-request permission to make sure granted state will be reached - permissionState.launchPermissionRequest() - } - } else { - if (callbackResult == false && permissionState.status.shouldShowRationale) { - onDeniedForever(permissionState) - } else { - onShowInfo(permissionState) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.android.kt deleted file mode 100644 index 10ecca9b..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.android.kt +++ /dev/null @@ -1,57 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.isPackageInstalled -import dev.datlag.burningseries.shared.common.openInBrowser -import dev.datlag.burningseries.shared.other.Project -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun ProjectCard(project: Project, modifier: Modifier) { - val context = LocalContext.current - - if (!context.isPackageInstalled(project.`package`)) { - ElevatedCard( - modifier = modifier, - onClick = { - (project.googlePlay ?: project.github)?.openInBrowser(context) - } - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - project.icon?.let { - Image( - modifier = Modifier.size(64.dp).clip(MaterialTheme.shapes.medium), - painter = painterResource(it), - contentDescription = stringResource(project.title), - ) - } - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(project.title), - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.titleLarge - ) - Text(text = stringResource(project.subTitle)) - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.android.kt deleted file mode 100644 index 61eb42ec..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.android.kt +++ /dev/null @@ -1,116 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.material3.* -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.model.Release -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.header -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.openInBrowser -import dev.datlag.burningseries.shared.other.DomainVerifier -import dev.datlag.burningseries.shared.rememberIsTv -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.StateFlow - -actual fun LazyGridScope.DeviceContent(release: StateFlow, onDeviceReachable: StateFlow) { - header { - val newRelease by release.collectAsStateWithLifecycle() - val reachable by onDeviceReachable.collectAsStateWithLifecycle() - - if (newRelease != null) { - Row( - modifier = Modifier.padding(top = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - val context = LocalContext.current - - Column( - modifier = Modifier.weight(1F), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = stringResource(SharedRes.strings.release_available_title), - style = MaterialTheme.typography.headlineMedium - ) - Text( - text = stringResource(SharedRes.strings.release_available_text, newRelease!!.title) - ) - } - Button( - onClick = { - newRelease!!.htmlUrl.openInBrowser(context) - } - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.GitHub), - contentDescription = stringResource(SharedRes.strings.github), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.github)) - } - } - } else { - val context = LocalContext.current - val verified by DomainVerifier.verified.collectAsStateWithLifecycle() - - SideEffect { - DomainVerifier.verify(context) - } - - if (!rememberIsTv() && !verified && DomainVerifier.supported) { - Column( - modifier = Modifier.padding(top = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(SharedRes.strings.open_domains_title), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - Text( - text = buildAnnotatedString { - withStyle( - SpanStyle( - fontWeight = FontWeight.Bold - ) - ) { - append(stringResource(SharedRes.strings.open_domains_text_1)) - } - appendLine() - append(stringResource(SharedRes.strings.open_domains_text_2)) - appendLine() - append(stringResource(SharedRes.strings.open_domains_text_3)) - } - ) - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { - DomainVerifier.enable(context) - } - ) { - Text(text = stringResource(SharedRes.strings.enable)) - } - } - } else if (!reachable) { - Text(text = stringResource(SharedRes.strings.enable_custom_dns)) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.android.kt deleted file mode 100644 index aa646edd..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.android.kt +++ /dev/null @@ -1,18 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import dev.datlag.burningseries.shared.ui.KeyEventDispatcher -import dev.datlag.burningseries.shared.ui.PIPActions -import dev.datlag.burningseries.shared.ui.PIPEnabled -import dev.datlag.burningseries.shared.ui.PIPModeListener - -@Composable -actual fun EnterSeriesScreen() { - SideEffect { - KeyEventDispatcher = { null } - PIPEnabled = false - PIPModeListener = { } - PIPActions = { null } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.android.kt deleted file mode 100644 index 02da980c..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.android.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component - -import android.annotation.SuppressLint -import android.webkit.WebView -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import com.google.accompanist.web.rememberWebViewState -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.withIOContext -import dev.datlag.burningseries.shared.common.withMainContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive - -@SuppressLint("SetJavaScriptEnabled") -@Composable -actual fun WebView(url: String, modifier: Modifier, onScraped: (String) -> Unit) { - val state = rememberWebViewState(url) - var webView = remember { null } - val scrapingJs = SharedRes.assets.scrape_hoster_android.readText(LocalContext.current) - - - com.google.accompanist.web.WebView( - state = state, - modifier = modifier, - captureBackPresses = true, - client = WebViewClient( - allowedHosts = setOf(BSUtil.HOST_BS_TO) - ), - onCreated = { - it.settings.allowFileAccess = false - it.settings.javaScriptEnabled = true - it.settings.javaScriptCanOpenWindowsAutomatically = false - it.settings.mediaPlaybackRequiresUserGesture = true - webView = it - } - ) - - LaunchedEffect(webView) { - withIOContext { - do { - delay(3000) - withMainContext { - webView?.evaluateJavascript(scrapingJs) { result -> - result?.let(onScraped) - } - } - } while (isActive && webView != null) - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.android.kt deleted file mode 100644 index a5458a78..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.android.kt +++ /dev/null @@ -1,53 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun ErrorDialog(component: ErrorComponent) { - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.activate_error_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = stringResource(SharedRes.strings.activate_error_text)) - }, - confirmButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - }, - dismissButton = if (component.stream != null) { - { - Button( - onClick = { - component.watch(component.stream!!) - } - ) { - Text(text = stringResource(SharedRes.strings.watch)) - } - } - } else { - null - } - ) -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.android.kt deleted file mode 100644 index d6b895db..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.android.kt +++ /dev/null @@ -1,53 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun SuccessDialog(component: SuccessComponent) { - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.activate_success_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = stringResource(SharedRes.strings.activate_success_text)) - }, - confirmButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - }, - dismissButton = if (component.stream != null) { - { - Button( - onClick = { - component.watch(component.stream!!) - } - ) { - Text(text = stringResource(SharedRes.strings.watch)) - } - } - } else { - null - } - ) -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.android.kt deleted file mode 100644 index f490bf5b..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.android.kt +++ /dev/null @@ -1,78 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.isPackageInstalled -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.other.Project -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.StateFlow - -/** - * Keep single implementation instead of ProjectCard, to support hint for not connected. - */ -@Composable -actual fun AniFlowCard( - isAnime: StateFlow, - modifier: Modifier -) { - val context = LocalContext.current - val project = Project.AniFlow - - if (!context.isPackageInstalled(project.`package`)) { - val anime by isAnime.collectAsStateWithLifecycle() - - if (anime) { - val uriHandler = LocalUriHandler.current - - Card( - modifier = modifier, - onClick = { - uriHandler.openUri(project.googlePlay ?: project.github) - } - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - project.icon?.let { - Image( - modifier = Modifier.size(48.dp).clip(MaterialTheme.shapes.medium), - painter = painterResource(it), - contentDescription = stringResource(project.title), - ) - } - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(project.title), - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.titleLarge - ) - Text(text = stringResource(project.subTitle)) - } - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.android.kt deleted file mode 100644 index c4d863fe..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.android.kt +++ /dev/null @@ -1,457 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import android.Manifest -import android.app.Notification -import android.app.NotificationManager -import android.content.pm.PackageManager -import android.media.session.MediaSession.Token -import android.view.LayoutInflater -import android.view.View -import android.view.WindowManager -import android.widget.FrameLayout.LayoutParams -import android.widget.ImageButton -import android.widget.TextView -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationChannelCompat -import androidx.core.app.NotificationManagerCompat -import androidx.core.view.WindowInsetsControllerCompat -import androidx.media3.cast.CastPlayer -import androidx.media3.cast.SessionAvailabilityListener -import androidx.media3.common.* -import androidx.media3.datasource.DefaultDataSource -import androidx.media3.datasource.DefaultHttpDataSource -import androidx.media3.exoplayer.ExoPlayer -import androidx.media3.exoplayer.source.DefaultMediaSourceFactory -import androidx.media3.extractor.DefaultExtractorsFactory -import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.* -import androidx.media3.session.MediaSession -import androidx.media3.ui.DefaultTimeBar -import androidx.media3.ui.PlayerView -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.google.android.gms.cast.framework.CastState -import com.google.android.material.progressindicator.CircularProgressIndicator -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.shared.R -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.findWindow -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.withIOContext -import dev.datlag.burningseries.shared.common.withMainContext -import dev.datlag.burningseries.shared.rememberIsTv -import dev.datlag.burningseries.shared.ui.* -import dev.datlag.kast.ConnectionState -import dev.datlag.kast.Kast -import dev.datlag.kast.UnselectReason -import dev.datlag.nanoid.NanoIdUtils -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import java.util.* -import kotlin.random.Random - -val PseudoRandom = Random(12345) // pseudo random as secure random is not needed - -@Composable -@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) -actual fun VideoScreen(component: VideoComponent) { - val context = LocalContext.current - val castContext = Kast.castContext - val connectionState by Kast.connectionState.collectAsStateWithLifecycle() - val castButtonConnected = remember(connectionState) { - when (connectionState) { - is ConnectionState.DISCONNECTED -> false - else -> true - } - } - val dialogState by component.dialog.subscribeAsState() - - val streamList = remember { component.streams } - - var streamIndex by remember(streamList) { mutableIntStateOf(0) } - var sourceIndex by remember(streamIndex) { mutableIntStateOf(0) } - val headers by remember(streamIndex) { - mutableStateOf(streamList[streamIndex].headers) - } - val mediaItem = remember(streamList, streamIndex, sourceIndex) { - MediaItem.fromUri(streamList[streamIndex].sources.toList()[sourceIndex]) - } - val startingPos by component.startingPos.collectAsStateWithLifecycle() - - val castState by remember(castContext) { mutableStateOf(castContext?.castState) } - val casting by remember(castState) { mutableStateOf(castState == CastState.CONNECTED || castState == CastState.CONNECTING) } - var cast by remember(casting) { mutableStateOf(casting) } - val useCastPlayer = remember(cast) { cast } - - val sessionListener = remember { object : SessionAvailabilityListener { - override fun onCastSessionAvailable() { - cast = true - } - - override fun onCastSessionUnavailable() { - cast = false - } - } } - - var subtitles by remember { mutableStateOf(emptyList()) } - val selectedLanguage by component.selectedSubtitle.collectAsStateWithLifecycle() - - fun updateSubtitles(tracks: Tracks) { - val languages = tracks.groups.mapNotNull { group -> - if (group.type != C.TRACK_TYPE_TEXT) { - return@mapNotNull null - } - - val formats = (0 until group.length).map { index -> - group.getTrackFormat(index) - } - formats.mapNotNull { format -> - format.language - } - }.flatten().toSet().mapNotNull { - var title = Locale(it).displayLanguage - if (title == it) { - val code = it.split("[-_]".toRegex()).firstOrNull() ?: it - title = scopeCatching { - Locale.Builder().setLanguage(code).build() - }.getOrNull()?.displayName ?: scopeCatching { - Locale.forLanguageTag(code) - }.getOrNull()?.displayName ?: return@mapNotNull null - } - if (title != it) { - title += " ($it)" - } - - VideoComponent.Subtitle( - code = it, - title = title - ) - } - - subtitles = languages - } - - var displayLoading by remember { mutableStateOf(true) } - - val playListener = remember { object : Player.Listener { - override fun onPlayerError(error: PlaybackException) { - super.onPlayerError(error) - - if (streamList[streamIndex].sources.size - 1 > sourceIndex) { - sourceIndex++ - } else if (streamList.size - 1 > streamIndex) { - streamIndex++ - } - } - - override fun onTracksChanged(tracks: Tracks) { - super.onTracksChanged(tracks) - - updateSubtitles(tracks) - } - - override fun onPlaybackStateChanged(playbackState: Int) { - super.onPlaybackStateChanged(playbackState) - - when (playbackState) { - Player.STATE_ENDED -> { - displayLoading = false - component.ended() - } - Player.STATE_IDLE, Player.STATE_BUFFERING -> { - displayLoading = true - } - else -> { - displayLoading = false - } - } - } - } } - - val castPlayer = remember(castContext) { - if (castContext != null) { - CastPlayer(castContext) - } else { - null - } - } - - LaunchedEffect(castPlayer) { - castPlayer?.playWhenReady = true - castPlayer?.setSessionAvailabilityListener(sessionListener) - castPlayer?.addListener(playListener) - } - - val extractorFactory = remember { - DefaultExtractorsFactory().setTsExtractorFlags( - FLAG_ALLOW_NON_IDR_KEYFRAMES and FLAG_DETECT_ACCESS_UNITS and FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS - ) - } - - val dataSource = remember(headers) { - DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory() - .setDefaultRequestProperties(headers) - .setAllowCrossProtocolRedirects(true) - .setKeepPostFor302Redirects(true) - ) - } - - val localPlayer = remember(context, dataSource) { - ExoPlayer.Builder(context).apply { - setSeekBackIncrementMs(10000) - setSeekForwardIncrementMs(10000) - setMediaSourceFactory(DefaultMediaSourceFactory(dataSource, extractorFactory)) - }.build() - } - - LaunchedEffect(localPlayer) { - localPlayer.addListener(playListener) - localPlayer.playWhenReady = true - } - - DisposableEffect(localPlayer) { - onDispose { - localPlayer.release() - } - } - - val usingPlayer = remember(castPlayer, localPlayer, useCastPlayer) { - if (useCastPlayer) { - castPlayer ?: localPlayer - } else { - localPlayer - } - } - - val session = remember(usingPlayer) { - val nanoId = NanoIdUtils.randomNanoId(random = PseudoRandom) - MediaSession.Builder(context, usingPlayer).setId(nanoId).build() - } - - DisposableEffect(session) { - onDispose { - session.release() - } - } - - val mediaStyle = remember(session) { - Notification.MediaStyle().setMediaSession(session.sessionCompatToken.token as Token) - } - val channelName = stringResource(SharedRes.strings.channel_videoplayer_title) - val channelText = stringResource(SharedRes.strings.channel_videoplayer_text) - val channel = remember(channelName, channelText) { - NotificationChannelCompat.Builder("Cast", NotificationManager.IMPORTANCE_LOW) - .setName(channelName) - .setDescription(channelText) - .build() - } - - LaunchedEffect(channel) { - NotificationManagerCompat.from(context).createNotificationChannel(channel) - } - - val notification = remember(mediaStyle, channel) { - (100..200).random() to Notification.Builder(context, channel.id) - .setStyle(mediaStyle) - .setSmallIcon(SmallIcon) - .build() - } - - LaunchedEffect(notification) { - if (!NotificationPermission) { - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { - NotificationPermission = true - } - } - - if (NotificationPermission) { - NotificationManagerCompat.from(context).notify(notification.first, notification.second) - } - } - - DisposableEffect(notification) { - onDispose { - NotificationManagerCompat.from(context).cancel(notification.first) - } - } - - LaunchedEffect(usingPlayer, mediaItem) { - val media = if (usingPlayer is CastPlayer) { - val mimeType = mediaItem.localConfiguration?.mimeType ?: MimeTypes.VIDEO_UNKNOWN - mediaItem.buildUpon().setMimeType(mimeType).build() - } else { - mediaItem - } - usingPlayer.setMediaItem(media, startingPos) - usingPlayer.prepare() - - updateSubtitles(usingPlayer.currentTracks) - - withIOContext { - do { - delay(3000) - withMainContext { - component.lengthUpdate(usingPlayer.duration) - component.progressUpdate(usingPlayer.currentPosition) - } - } while (isActive) - } - } - - LaunchedEffect(usingPlayer, subtitles, selectedLanguage) { - val chosen = subtitles.firstOrNull { it.code == selectedLanguage?.code } - usingPlayer.trackSelectionParameters = usingPlayer - .trackSelectionParameters - .buildUpon() - .setPreferredTextLanguage(chosen?.code) - .build() - } - - DisposableEffect(usingPlayer) { - onDispose { - usingPlayer.release() - } - } - - val progressColor = MaterialTheme.colorScheme.primary.toArgb() - val episode by component.episode.collectAsStateWithLifecycle() - val isTv = rememberIsTv() - - AndroidView( - modifier = Modifier.fillMaxSize().background(Color.Black), - factory = { viewContext -> - val view = LayoutInflater.from(viewContext).inflate(R.layout.video_player, null, false) - - view.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - view.setBackgroundColor(android.graphics.Color.BLACK) - view.keepScreenOn = true - - val playerView = view.findViewById(R.id.player) - val controls = playerView.findViewById(R.id.exoplayer_controls) - val backButton = controls.findViewById(R.id.back_button) - val mediaRouteButton = controls.findViewById(R.id.cast_button) - val subtitleButton = controls.findViewById(R.id.subtitle) - - backButton.setOnClickListener { - component.back() - } - - val initialIcon = if (castButtonConnected) { - R.drawable.baseline_cast_connected_24 - } else { - R.drawable.baseline_cast_24 - } - mediaRouteButton.setImageResource(initialIcon) - mediaRouteButton.setOnClickListener { - usingPlayer.pause() - component.selectCast() - } - mediaRouteButton.visibility = if (isTv) { - View.GONE - } else { - View.VISIBLE - } - mediaRouteButton.isEnabled = !isTv - - subtitleButton.setOnClickListener { - playerView.player?.pause() - component.selectSubtitle(subtitles) - } - - KeyEventDispatcher = { event -> - event?.let { playerView.dispatchKeyEvent(it) } - } - PIPEnabled = true - PIPModeListener = { isInPIP -> - if (isInPIP) { - controls.visibility = View.GONE - } else { - controls.visibility = View.VISIBLE - } - } - - view - }, - update = { view -> - val playerView = view.findViewById(R.id.player) - val controls = playerView.findViewById(R.id.exoplayer_controls) - val title = controls.findViewById(R.id.title) - val subtitleButton = controls.findViewById(R.id.subtitle) - val progress = controls.findViewById(R.id.exo_progress) - val mediaRouteButton = controls.findViewById(R.id.cast_button) - val loadingIndicator = controls.findViewById(R.id.loading_indicator) - - playerView.player = usingPlayer - - title.text = episode.episodeTitle - - val castIcon = if (castButtonConnected) { - R.drawable.baseline_cast_connected_24 - } else { - R.drawable.baseline_cast_24 - } - mediaRouteButton.setImageResource(castIcon) - - loadingIndicator.setIndicatorColor(progressColor) - loadingIndicator.visibility = if (displayLoading) { - View.VISIBLE - } else { - View.GONE - } - - if (subtitles.isNotEmpty()) { - subtitleButton.visibility = View.VISIBLE - subtitleButton.isEnabled = true - } else { - subtitleButton.visibility = View.INVISIBLE - subtitleButton.isEnabled = false - } - - progress.setPlayedColor(progressColor) - progress.setScrubberColor(progressColor) - } - ) - - RequireFullScreen() - - DisposableEffect(Unit) { - onDispose { - KeyEventDispatcher = { null } - PIPEnabled = false - PIPModeListener = { } - PIPActions = { null } - Kast.unselect(UnselectReason.stopped) - } - } - - dialogState.child?.instance?.render() -} - -@Composable -private fun RequireFullScreen() { - val window = LocalView.current.context.findWindow() ?: LocalContext.current.findWindow() - val systemUiController = rememberSystemUiController(window) - - DisposableEffect(Unit) { - val originalBehavior = systemUiController.systemBarsBehavior - - systemUiController.isSystemBarsVisible = false - systemUiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON and WindowManager.LayoutParams.FLAG_SECURE) - onDispose { - systemUiController.isSystemBarsVisible = true - systemUiController.systemBarsBehavior = originalBehavior - window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON and WindowManager.LayoutParams.FLAG_SECURE) - } - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.android.kt deleted file mode 100644 index a89bfe13..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.android.kt +++ /dev/null @@ -1,186 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.cast - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Cast -import androidx.compose.material.icons.filled.SmartDisplay -import androidx.compose.material.icons.filled.Speaker -import androidx.compose.material.icons.filled.Tv -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.onClick -import dev.datlag.kast.* -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.map - -@Composable -actual fun CastDialog(component: CastComponent) { - val selectedItem by Kast.selectedDevice.collectAsStateWithLifecycle() - val otherItems by remember { Kast.allAvailableDevices.map { list -> list.mapNotNull { - if (it.selected) { - null - } else { - it - } - } } }.collectAsStateWithLifecycle(initialValue = emptyList()) - var closeRequest by remember { mutableStateOf(false) } - - SideEffect { - Kast.Router.activeDiscovery() - } - - DisposableEffect(Unit) { - onDispose { - Kast.Router.passiveDiscovery() - } - } - - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - icon = { - Icon( - imageVector = Icons.Default.Cast, - contentDescription = stringResource(SharedRes.strings.casting) - ) - }, - title = { - Text( - text = stringResource(SharedRes.strings.casting), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Column( - modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (selectedItem != null) { - Text( - text = stringResource(SharedRes.strings.selected), - fontWeight = FontWeight.Bold - ) - DeviceInfo( - device = selectedItem!!, - onClick = { - Kast.unselect(UnselectReason.disconnected) - component.dismiss() - }, - onConnected = { - if (closeRequest) { - component.dismiss() - } - } - ) - } - if (otherItems.isEmpty() && selectedItem == null) { - Text( - text = stringResource(SharedRes.strings.available), - fontWeight = FontWeight.Bold - ) - CircularProgressIndicator( - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - } else if (selectedItem == null) { - Text( - text = stringResource(SharedRes.strings.available), - fontWeight = FontWeight.Bold - ) - - otherItems.forEach { - DeviceInfo( - device = it, - onClick = { - closeRequest = true - Kast.select(it) - }, - onConnected = { - if (closeRequest) { - component.dismiss() - } - } - ) - } - } - } - }, - confirmButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - }, - dismissButton = { - if (selectedItem != null) { - Button( - onClick = { - Kast.unselect(UnselectReason.disconnected) - component.dismiss() - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Text(text = stringResource(SharedRes.strings.disconnect)) - } - } - } - ) -} - -@Composable -private fun DeviceInfo(device: Device, onClick: () -> Unit, onConnected: () -> Unit) { - val icon = when (device.type) { - is DeviceType.TV -> Icons.Default.Tv - is DeviceType.SPEAKER -> Icons.Default.Speaker - else -> Icons.Default.SmartDisplay - } - val isConnecting = when (device.connectionState) { - is ConnectionState.CONNECTING -> true - is ConnectionState.CONNECTED -> { - onConnected() - false - } - else -> false - } - - Row( - modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).clip(MaterialTheme.shapes.small).onClick { onClick() }, - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = icon, - contentDescription = device.name - ) - if (isConnecting) { - CircularProgressIndicator() - } - } - Text( - text = device.name, - overflow = TextOverflow.Ellipsis, - softWrap = true, - maxLines = 1 - ) - } -} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.android.kt b/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.android.kt deleted file mode 100644 index 0017353d..00000000 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.android.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import androidx.compose.material3.ColorScheme -import androidx.compose.runtime.Composable - -@Composable -actual fun SchemeThemeSystemProvider( - scheme: ColorScheme, - content: @Composable () -> Unit -) { - content() -} diff --git a/app/shared/src/androidMain/res/drawable/baseline_arrow_back_ios_new_24.xml b/app/shared/src/androidMain/res/drawable/baseline_arrow_back_ios_new_24.xml deleted file mode 100644 index 6bd5650e..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_arrow_back_ios_new_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/shared/src/androidMain/res/drawable/baseline_cast_24.xml b/app/shared/src/androidMain/res/drawable/baseline_cast_24.xml deleted file mode 100644 index e3e6c534..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_cast_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/shared/src/androidMain/res/drawable/baseline_cast_connected_24.xml b/app/shared/src/androidMain/res/drawable/baseline_cast_connected_24.xml deleted file mode 100644 index 048c2e6f..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_cast_connected_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/shared/src/androidMain/res/drawable/baseline_forward_10_24.xml b/app/shared/src/androidMain/res/drawable/baseline_forward_10_24.xml deleted file mode 100644 index e48dffa8..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_forward_10_24.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/shared/src/androidMain/res/drawable/baseline_replay_10_24.xml b/app/shared/src/androidMain/res/drawable/baseline_replay_10_24.xml deleted file mode 100644 index fad085f6..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_replay_10_24.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/shared/src/androidMain/res/drawable/baseline_subtitles_24.xml b/app/shared/src/androidMain/res/drawable/baseline_subtitles_24.xml deleted file mode 100644 index f03d23d2..00000000 --- a/app/shared/src/androidMain/res/drawable/baseline_subtitles_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/shared/src/androidMain/res/layout/exoplayer_controls.xml b/app/shared/src/androidMain/res/layout/exoplayer_controls.xml deleted file mode 100644 index 7d5b4583..00000000 --- a/app/shared/src/androidMain/res/layout/exoplayer_controls.xml +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/shared/src/androidMain/res/layout/video_player.xml b/app/shared/src/androidMain/res/layout/video_player.xml deleted file mode 100644 index a5f67218..00000000 --- a/app/shared/src/androidMain/res/layout/video_player.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/App.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/App.kt deleted file mode 100644 index b3051c04..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/App.kt +++ /dev/null @@ -1,56 +0,0 @@ -package dev.datlag.burningseries.shared - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.Modifier -import dev.chrisbanes.haze.HazeState -import dev.datlag.burningseries.shared.ui.theme.Colors -import dev.datlag.burningseries.shared.ui.theme.CommonSchemeTheme -import dev.datlag.burningseries.shared.ui.theme.ManropeTypography -import org.kodein.di.DI - -val LocalDarkMode = compositionLocalOf { error("No dark mode state provided") } -val LocalDI = compositionLocalOf { error("No dependency injection provided") } -val LocalHaze = compositionLocalOf { error("No Haze state provided") } -val LocalPaddingValues = compositionLocalOf { null } - -@Composable -fun App( - di: DI, - systemDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - CompositionLocalProvider( - LocalDarkMode provides systemDarkTheme, - LocalDI provides di - ) { - MaterialTheme( - colorScheme = if (systemDarkTheme) Colors.getDarkScheme() else Colors.getLightScheme(), - typography = ManropeTypography() - ) { - SystemProvider { - CommonSchemeTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, - contentColor = MaterialTheme.colorScheme.onBackground - ) { - content() - } - } - } - } - } -} - -@Composable -expect fun SystemProvider(content: @Composable () -> Unit) - -@Composable -expect fun rememberIsTv(): Boolean \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/PackageName.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/PackageName.kt deleted file mode 100644 index b3280163..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/PackageName.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.datlag.burningseries.shared - -expect fun getPackageName(): String \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendBlurHash.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendBlurHash.kt deleted file mode 100644 index 0c152e27..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendBlurHash.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import com.vanniktech.blurhash.BlurHash - -fun BlurHash.random(): String { - val list = listOf( - "LEHLh[WB2yk8pyoJadR*.7kCMdnj", - "LKN]Rv%2Tw=w]~RBVZRi};RPxuwH", - "L4D9hwcE00~WL#V@%~\$%00ad~qIA", - "LBFh@Z=wrY\$_~obE\$+t6NPIpIqa7", - "LLJGsB{y0NNY^jn\$RPXTso\$zt7R*", - "LSNAhr00.TbI%M-=%NayS\$f9IUM{" - ) - return list.random() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCompose.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCompose.kt deleted file mode 100644 index 180a7806..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCompose.kt +++ /dev/null @@ -1,341 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.background -import androidx.compose.foundation.focusable -import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.foundation.gestures.waitForUpOrCancellation -import androidx.compose.foundation.hoverable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState -import androidx.compose.foundation.interaction.collectIsHoveredAsState -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyGridItemScope -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.material3.ButtonColors -import androidx.compose.material3.ButtonDefaults -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.vector.PathBuilder -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max -import dev.datlag.burningseries.model.common.collectSafe -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.LocalPaddingValues -import dev.datlag.burningseries.shared.ui.theme.shape.DiagonalShape -import kotlin.math.max - -inline fun Modifier.ifTrue(predicate: Boolean, builder: Modifier.() -> Modifier) = then(if (predicate) builder() else Modifier) -inline fun Modifier.ifFalse(predicate: Boolean, builder: Modifier.() -> Modifier) = then(if (!predicate) builder() else Modifier) - -fun LazyGridScope.header( - content: @Composable LazyGridItemScope.() -> Unit -) { - item(span = { GridItemSpan(this.maxLineSpan) }, content = content) -} - -fun Modifier.bounceClick(minScale: Float = 0.9F) = composed { - var buttonState by remember { mutableStateOf(false) } - val scale by animateFloatAsState(if (buttonState) minScale else 1F) - - graphicsLayer { - scaleX = scale - scaleY = scale - }.pointerInput(buttonState) { - awaitPointerEventScope { - buttonState = if (buttonState) { - waitForUpOrCancellation() - false - } else { - awaitFirstDown(false) - true - } - } - } -} - -fun Modifier.pressClick(maxTranslation: Float = 10F) = composed { - var buttonState by remember { mutableStateOf(false) } - val translation by animateFloatAsState(if (buttonState) maxTranslation else 0F) - - graphicsLayer { - translationY = translation - }.pointerInput(buttonState) { - awaitPointerEventScope { - buttonState = if (buttonState) { - waitForUpOrCancellation() - false - } else { - awaitFirstDown(false) - true - } - } - } -} - -fun Modifier.diagonalShape( - angle: Float, - position: DiagonalShape.POSITION = DiagonalShape.POSITION.TOP -) = this.clip(DiagonalShape(angle, position)) - -fun DpSize.toSize(): Size { - return Size( - width = this.width.value, - height = this.height.value - ) -} - -@Composable -fun LazyListState.OnBottomReached(enabled: Boolean = true, buffer: Int = 0, block: () -> Unit) { - if (enabled) { - val maxBuffer = max(0, buffer) - - val shouldCallBlock = remember { - derivedStateOf { - val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true - lastVisibleItem.index == layoutInfo.totalItemsCount - 1 - maxBuffer - } - } - - LaunchedEffect(shouldCallBlock) { - snapshotFlow { shouldCallBlock.value }.collectSafe { - if (it) { - block() - } - } - } - } -} - -@Composable -fun Modifier.isFocused( - hoverable: Boolean = true, - focusable: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - builder: Modifier.() -> Modifier -): Modifier = composed { - val isHovered by interactionSource.collectIsHoveredAsState() - val isFocused by interactionSource.collectIsFocusedAsState() - - this.ifTrue(isHovered || isFocused) { - builder() - }.hoverable( - interactionSource = interactionSource, - enabled = hoverable - ).focusable( - interactionSource = interactionSource, - enabled = focusable - ) -} - -@Composable -fun Modifier.onFocusChanged( - hoverable: Boolean = true, - focusable: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onChanged: (Boolean) -> Unit -): Modifier = composed { - val isHovered by interactionSource.collectIsHoveredAsState() - val isFocused by interactionSource.collectIsFocusedAsState() - - val latestValue = remember(isHovered, isFocused) { isHovered || isFocused } - - LaunchedEffect(latestValue) { - onChanged(latestValue) - } - - this.hoverable( - interactionSource = interactionSource, - enabled = hoverable - ).focusable( - interactionSource = interactionSource, - enabled = focusable - ) -} - -@Composable -fun Modifier.focusScale( - scale: Float = 1.1F, - hoverable: Boolean = true, - focusable: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } -) = composed { - isFocused( - hoverable, - focusable, - interactionSource - ) { - graphicsLayer { - scaleX = scale - scaleY = scale - } - } -} - -fun PathBuilder.drawPathFromSvgData(data: String) { - val pathCommands = data.split("(?=[a-zA-Z])".toRegex()).filterNot { it.isBlank() } - val numberRegex = "[-+]?\\d*\\.?\\d+".toRegex() - - for (command in pathCommands) { - val cmd = command.substring(0, 1).trim() - val coords = numberRegex.findAll(command.substring(1)).mapNotNull { it.value.toFloatOrNull() }.toList() - println(coords) - - when (cmd) { - "M" -> moveTo(coords[0], coords[1]) - "m" -> moveToRelative(coords[0], coords[1]) - "L" -> lineTo(coords[0], coords[1]) - "l" -> lineToRelative(coords[0], coords[1]) - "Q" -> quadTo(coords[0], coords[1], coords[2], coords[3]) - "q" -> quadToRelative(coords[0], coords[1], coords[2], coords[3]) - "T" -> reflectiveQuadTo(coords[0], coords[1]) - "t" -> reflectiveQuadToRelative(coords[0], coords[1]) - "C" -> curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]) - "c" -> curveToRelative(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]) - "S" -> reflectiveCurveTo(coords[0], coords[1], coords[2], coords[3]) - "s" -> reflectiveCurveToRelative(coords[0], coords[1], coords[2], coords[3]) - "V" -> verticalLineTo(coords[0]) - "v" -> verticalLineToRelative(coords[0]) - "H" -> horizontalLineTo(coords[0]) - "h" -> horizontalLineToRelative(coords[0]) - "Z", "z" -> close() - } - } -} - -@Composable -operator fun PaddingValues.plus(other: PaddingValues): PaddingValues { - val direction = LocalLayoutDirection.current - - return PaddingValues( - start = this.calculateStartPadding(direction) + other.calculateStartPadding(direction), - top = this.calculateTopPadding() + other.calculateTopPadding(), - end = this.calculateEndPadding(direction) + other.calculateEndPadding(direction), - bottom = this.calculateBottomPadding() + other.calculateBottomPadding() - ) -} - -@Composable -fun PaddingValues.merge(other: PaddingValues): PaddingValues { - val direction = LocalLayoutDirection.current - - return PaddingValues( - start = max(this.calculateStartPadding(direction), other.calculateStartPadding(direction)), - top = max(this.calculateTopPadding(), other.calculateTopPadding()), - end = max(this.calculateEndPadding(direction), other.calculateEndPadding(direction)), - bottom = max(this.calculateBottomPadding(), other.calculateBottomPadding()) - ) -} - -fun Modifier.localPadding(additional: PaddingValues = PaddingValues(0.dp)) = composed { - this.padding(LocalPaddingValues.current?.plus(additional) ?: additional) -} - -fun Modifier.localPadding(all: Dp) = composed { - this.localPadding(PaddingValues(all)) -} - -fun Modifier.localPadding(horizontal: Dp, vertical: Dp = 0.dp) = composed { - this.localPadding(PaddingValues(horizontal = horizontal, vertical = vertical)) -} - -@Composable -fun LocalPadding(additional: PaddingValues = PaddingValues(0.dp)): PaddingValues { - return LocalPaddingValues.current?.plus(additional) ?: additional -} - -@Composable -fun LocalPadding(additional: Dp): PaddingValues { - return LocalPaddingValues.current?.plus(PaddingValues(additional)) ?: PaddingValues(additional) -} - -@Composable -fun LocalPadding(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): PaddingValues { - return LocalPaddingValues.current?.plus( - PaddingValues(horizontal = horizontal, vertical = vertical) - ) ?: PaddingValues(horizontal = horizontal, vertical = vertical) -} - -fun Modifier.mergedLocalPadding(other: PaddingValues, additional: PaddingValues = PaddingValues(0.dp)) = composed { - this.padding((LocalPaddingValues.current?.merge(other) ?: other).plus(additional)) -} - -fun Modifier.mergedLocalPadding(other: PaddingValues, additional: Dp) = composed { - this.mergedLocalPadding(other, PaddingValues(additional)) -} - -fun Modifier.bottomShadowBrush(color: Color, alpha: Float = 1F): Modifier { - val maxAlpha = kotlin.math.min(alpha, 1F) - - return this.background( - brush = Brush.verticalGradient( - 0.0f to Color.Transparent, - 0.1f to color.copy(alpha = 0.35f * maxAlpha), - 0.3f to color.copy(alpha = 0.55f * maxAlpha), - 0.5f to color.copy(alpha = 0.75f * maxAlpha), - 0.7f to color.copy(alpha = 0.95f * maxAlpha), - 0.9f to color.copy(alpha = 1f * maxAlpha) - ) - ) -} - -@Composable -fun ButtonDefaults.githubColors(): ButtonColors { - return if (LocalDarkMode.current) { - ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = Color(0xFF121212) - ) - } else { - ButtonDefaults.buttonColors( - containerColor = Color.Black, - contentColor = Color(0xFFFAFAFA) - ) - } -} - -@Composable -fun ButtonDefaults.polarColors(): ButtonColors { - return ButtonDefaults.buttonColors( - containerColor = Color(0xFF0062FF), - contentColor = Color(0xFFFAFAFA) - ) -} - -@Composable -fun ButtonDefaults.patreonColors(): ButtonColors { - return ButtonDefaults.buttonColors( - containerColor = Color(0xFFF96854), - contentColor = Color(0xFFFAFAFA) - ) -} - -@Composable -fun ButtonDefaults.paypalColors(): ButtonColors { - return if (LocalDarkMode.current) { - ButtonDefaults.buttonColors( - containerColor = Color(0xFF009CDE), - contentColor = Color(0xFFFAFAFA) - ) - } else { - ButtonDefaults.buttonColors( - containerColor = Color(0xFF003087), - contentColor = Color(0xFFFAFAFA) - ) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCoroutine.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCoroutine.kt deleted file mode 100644 index 9d93d849..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendCoroutine.kt +++ /dev/null @@ -1,104 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import com.arkivanov.essenty.lifecycle.doOnDestroy -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlin.coroutines.CoroutineContext - -fun MutableStateFlow.safeEmit(value: T, scope: CoroutineScope) { - if (!this.tryEmit(value)) { - scope.launch(ioDispatcher()) { - this@safeEmit.emit(value) - } - } -} - -fun CoroutineScope(context: CoroutineContext, lifecycle: Lifecycle): CoroutineScope { - val scope = CoroutineScope(context) - lifecycle.doOnDestroy(scope::cancel) - return scope -} - -fun LifecycleOwner.coroutineScope(context: CoroutineContext): CoroutineScope = CoroutineScope(context, lifecycle) - -fun LifecycleOwner.ioScope() = CoroutineScope(ioDispatcher() + SupervisorJob(), lifecycle) -fun LifecycleOwner.mainScope() = CoroutineScope(mainDispatcher() + SupervisorJob(), lifecycle) -fun LifecycleOwner.defaultScope() = CoroutineScope(defaultDispatcher() + SupervisorJob(), lifecycle) - -fun mainDispatcher(): MainCoroutineDispatcher = Dispatchers.DeviceMain -fun ioDispatcher(): CoroutineDispatcher = Dispatchers.DeviceIO -fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.DeviceDefault - -fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job { - return this.launch(ioDispatcher()) { - block() - } -} - -fun CoroutineScope.launchMain(block: suspend CoroutineScope.() -> Unit): Job { - return this.launch(mainDispatcher()) { - block() - } -} - -fun CoroutineScope.launchDefault(block: suspend CoroutineScope.() -> Unit): Job { - return this.launch(defaultDispatcher()) { - block() - } -} - -fun LifecycleOwner.launchIO(block: suspend CoroutineScope.() -> Unit): Job { - return ioScope().launchIO(block) -} - -fun LifecycleOwner.launchMain(block: suspend CoroutineScope.() -> Unit): Job { - return mainScope().launchMain(block) -} - -fun LifecycleOwner.launchDefault(block: suspend CoroutineScope.() -> Unit): Job { - return defaultScope().launchDefault(block) -} - -suspend fun withIOContext( - block: suspend CoroutineScope.() -> T -): T { - return withContext(ioDispatcher()) { - block() - } -} - -suspend fun withMainContext( - block: suspend CoroutineScope.() -> T -): T { - return withContext(mainDispatcher()) { - block() - } -} - -suspend fun withDefaultContext( - block: suspend CoroutineScope.() -> T -): T { - return withContext(defaultDispatcher()) { - block() - } -} - -fun runBlockingIO(block: suspend CoroutineScope.() -> T): T { - return runBlocking(ioDispatcher()) { - block() - } -} - -fun runBlockingMain(block: suspend CoroutineScope.() -> T): T { - return runBlocking(mainDispatcher()) { - block() - } -} - -fun runBlockingDefault(block: suspend CoroutineScope.() -> T): T { - return runBlocking(defaultDispatcher()) { - block() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendString.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendString.kt deleted file mode 100644 index 638f61ac..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendString.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import androidx.compose.ui.text.AnnotatedString -import kotlin.math.max -import kotlin.math.min - -fun AnnotatedString.safeSubSequence(from: Int, to: Int): AnnotatedString { - if (this.isEmpty()) { - return this - } - - val safeFrom = max(min(from, lastIndex), 0) - return this.subSequence( - safeFrom, - max(safeFrom, min(to, length)) - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.kt deleted file mode 100644 index 3e1a8c5d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation -import com.arkivanov.essenty.backhandler.BackHandler -import com.vanniktech.blurhash.BlurHash -import org.kodein.di.DI - -expect fun Modifier.onClick( - enabled: Boolean = true, - onDoubleClick: (() -> Unit)? = null, - onLongClick: (() -> Unit)? = null, - onClick: () -> Unit -) : Modifier - -expect fun backAnimation( - backHandler: BackHandler, - onBack: () -> Unit -) : StackAnimation - -expect fun BlurHash.decode( - hash: String, - width: Int, - height: Int -): ImageBitmap? - -expect fun String.openInBrowser(di: DI) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.kt deleted file mode 100644 index f37b2d55..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainCoroutineDispatcher - -expect val Dispatchers.DeviceMain: MainCoroutineDispatcher -expect val Dispatchers.DeviceIO: CoroutineDispatcher -expect val Dispatchers.DeviceDefault: CoroutineDispatcher \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectAsStateWithLifecycle.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectAsStateWithLifecycle.kt deleted file mode 100644 index a1f18c3c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectAsStateWithLifecycle.kt +++ /dev/null @@ -1,223 +0,0 @@ -package dev.datlag.burningseries.shared.common.lifecycle - -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Ported collectAsStateWithLifecycle to Essenty Lifecycle - */ - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.produceState -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import dev.datlag.burningseries.model.common.collectSafe -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.withContext -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * Collects values from this [StateFlow] and represents its latest value via [State] in a - * lifecycle-aware manner. - * - * The [StateFlow.value] is used as an initial value. Every time there would be new value posted - * into the [StateFlow] the returned [State] will be updated causing recomposition of every - * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState]. - * - * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the - * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle - * falls below [minActiveState]. - * - * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this` - * flow. - * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The - * collection will stop if the lifecycle falls below that state, and will restart if it's in that - * state again. - * @param context [CoroutineContext] to use for collecting. - */ -@Composable -fun StateFlow.collectAsStateWithLifecycle( - lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED, - context: CoroutineContext = EmptyCoroutineContext -): State = collectAsStateWithLifecycle( - initialValue = this.value, - lifecycle = lifecycleOwner.lifecycle, - minActiveState = minActiveState, - context = context -) - -/** - * Collects values from this [StateFlow] and represents its latest value via [State] in a - * lifecycle-aware manner. - * - * The [StateFlow.value] is used as an initial value. Every time there would be new value posted - * into the [StateFlow] the returned [State] will be updated causing recomposition of every - * [State.value] usage whenever the [lifecycle] is at least [minActiveState]. - * - * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle - * state. The collection stops when [lifecycle] falls below [minActiveState]. - * - * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param lifecycle [Lifecycle] used to restart collecting `this` flow. - * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The - * collection will stop if the lifecycle falls below that state, and will restart if it's in that - * state again. - * @param context [CoroutineContext] to use for collecting. - */ -@Composable -fun StateFlow.collectAsStateWithLifecycle( - lifecycle: Lifecycle, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED, - context: CoroutineContext = EmptyCoroutineContext -): State = collectAsStateWithLifecycle( - initialValue = this.value, - lifecycle = lifecycle, - minActiveState = minActiveState, - context = context -) - -/** - * Collects values from this [Flow] and represents its latest value via [State] in a - * lifecycle-aware manner. - * - * Every time there would be new value posted into the [Flow] the returned [State] will be updated - * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is - * at least [minActiveState]. - * - * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState] - * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below - * [minActiveState]. - * - * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param initialValue The initial value given to the returned [State.value]. - * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this` - * flow. - * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The - * collection will stop if the lifecycle falls below that state, and will restart if it's in that - * state again. - * @param context [CoroutineContext] to use for collecting. - */ -@Composable -fun Flow.collectAsStateWithLifecycle( - initialValue: T, - lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED, - context: CoroutineContext = EmptyCoroutineContext -): State = collectAsStateWithLifecycle( - initialValue = initialValue, - lifecycle = lifecycleOwner.lifecycle, - minActiveState = minActiveState, - context = context -) - -/** - * Collects values from this [Flow] and represents its latest value via [State] in a - * lifecycle-aware manner. - * - * Every time there would be new value posted into the [Flow] the returned [State] will be updated - * causing recomposition of every [State.value] usage whenever the [lifecycle] is at - * least [minActiveState]. - * - * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle - * state. The collection stops when [lifecycle] falls below [minActiveState]. - * - * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param initialValue The initial value given to the returned [State.value]. - * @param lifecycle [Lifecycle] used to restart collecting `this` flow. - * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The - * collection will stop if the lifecycle falls below that state, and will restart if it's in that - * state again. - * @param context [CoroutineContext] to use for collecting. - */ -@Composable -fun Flow.collectAsStateWithLifecycle( - initialValue: T, - lifecycle: Lifecycle, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED, - context: CoroutineContext = EmptyCoroutineContext -): State { - return produceState(initialValue, this, lifecycle, minActiveState, context) { - lifecycle.repeatOnLifecycle(minActiveState) { - if (context == EmptyCoroutineContext) { - this@collectAsStateWithLifecycle.collectSafe { this@produceState.value = it } - } else withContext(context) { - this@collectAsStateWithLifecycle.collectSafe { this@produceState.value = it } - } - } - } -} - -/** - * Collects values from this [Flow] and represents its latest value via [State] in a - * lifecycle-aware manner. - * - * Every time there would be new value posted into the [Flow] the returned [State] will be updated - * causing recomposition of every [State.value] usage whenever the [lifecycle] is at - * least [minActiveState]. - * - * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle - * state. The collection stops when [lifecycle] falls below [minActiveState]. - * - * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param initialValue The initial value given to the returned [State.value]. - * @param lifecycle [Lifecycle] used to restart collecting `this` flow. - * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The - * collection will stop if the lifecycle falls below that state, and will restart if it's in that - * state again. - * @param context [CoroutineContext] to use for collecting. - */ -@Composable -fun StateFlow.collectAsStateWithLifecycle( - initialValue: T, - lifecycle: Lifecycle, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED, - context: CoroutineContext = EmptyCoroutineContext -): State { - return produceState(initialValue, this, lifecycle, minActiveState, context) { - lifecycle.repeatOnLifecycle(minActiveState) { - if (context == EmptyCoroutineContext) { - this@collectAsStateWithLifecycle.collectSafe { this@produceState.value = it } - } else withContext(context) { - this@collectAsStateWithLifecycle.collectSafe { this@produceState.value = it } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectOnLifecycle.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectOnLifecycle.kt deleted file mode 100644 index db21f9c2..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/CollectOnLifecycle.kt +++ /dev/null @@ -1,82 +0,0 @@ -package dev.datlag.burningseries.shared.common.lifecycle - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import dev.datlag.burningseries.model.common.collectSafe -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.callbackFlow - -suspend fun Flow.collectOnLifecycle( - lifecycle: Lifecycle, - state: Lifecycle.State = Lifecycle.State.STARTED, - collector: FlowCollector -) { - lifecycle.repeatOnLifecycle(state) { - collectSafe(collector) - } -} - -suspend fun Flow.collectOnLifecycle( - lifecycleOwner: LifecycleOwner, - state: Lifecycle.State = Lifecycle.State.STARTED, - collector: FlowCollector -) { - lifecycleOwner.repeatOnLifecycle(state) { - collectSafe(collector) - } -} - -@Composable -fun Flow.collectOnLocalLifecycle( - state: Lifecycle.State = Lifecycle.State.STARTED, - collector: FlowCollector -) { - val lifecycle = LocalLifecycleOwner.current - LaunchedEffect(this, lifecycle) { - lifecycle.repeatOnLifecycle(state) { - collectSafe(collector) - } - } -} - -fun Flow.flowWithLifecycle( - lifecycle: Lifecycle, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED -): Flow = callbackFlow { - lifecycle.repeatOnLifecycle(minActiveState) { - this@flowWithLifecycle.collectSafe { - send(it) - } - } - close() -} - -fun Flow.flowWithLifecycle( - lifecycle: LifecycleOwner, - minActiveState: Lifecycle.State = Lifecycle.State.STARTED -): Flow = callbackFlow { - lifecycle.repeatOnLifecycle(minActiveState) { - this@flowWithLifecycle.collectSafe { - send(it) - } - } - close() -} - -@Composable -fun Flow.withLocalLifecycle( - minActiveState: Lifecycle.State = Lifecycle.State.STARTED -): Flow { - val lifecycle = LocalLifecycleOwner.current - return callbackFlow { - lifecycle.repeatOnLifecycle(minActiveState) { - this@withLocalLifecycle.collectSafe { - send(it) - } - } - close() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/LocalLifecycleOwner.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/LocalLifecycleOwner.kt deleted file mode 100644 index 8f7ac658..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/LocalLifecycleOwner.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.common.lifecycle - -import androidx.compose.runtime.compositionLocalOf -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import com.arkivanov.essenty.lifecycle.LifecycleRegistry - -val LocalLifecycleOwner = compositionLocalOf { - object : LifecycleOwner { - override val lifecycle: Lifecycle = LifecycleRegistry() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/RepeatOnLifecycle.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/RepeatOnLifecycle.kt deleted file mode 100644 index ad4acd06..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/RepeatOnLifecycle.kt +++ /dev/null @@ -1,194 +0,0 @@ -package dev.datlag.burningseries.shared.common.lifecycle - -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Ported repeatOnLifecycle to Essenty Lifecycle - */ - -import com.arkivanov.essenty.lifecycle.Lifecycle -import com.arkivanov.essenty.lifecycle.LifecycleOwner -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlin.coroutines.resume - -/** - * Runs the given [block] in a new coroutine when `this` [Lifecycle] is at least at [state] and - * suspends the execution until `this` [Lifecycle] is [Lifecycle.State.DESTROYED]. - * - * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state. - * - * ``` - * class MyActivity : AppCompatActivity() { - * override fun onCreate(savedInstanceState: Bundle?) { - * /* ... */ - * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. - * // The coroutine will be cancelled when the ON_STOP event happens and will - * // restart executing if the lifecycle receives the ON_START event again. - * lifecycleScope.launch { - * lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - * uiStateFlow.collect { uiState -> - * updateUi(uiState) - * } - * } - * } - * } - * } - * ``` - * - * The best practice is to call this function when the lifecycle is initialized. For - * example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise, multiple - * repeating coroutines doing the same could be created and be executed at the same time. - * - * Repeated invocations of `block` will run serially, that is they will always wait for the - * previous invocation to fully finish before re-starting execution as the state moves in and out - * of the required state. - * - * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param state [Lifecycle.State] in which `block` runs in a new coroutine. That coroutine - * will cancel if the lifecycle falls below that state, and will restart if it's in that state - * again. - * @param block The block to run when the lifecycle is at least in [state] state. - */ -suspend fun Lifecycle.repeatOnLifecycle( - state: Lifecycle.State = Lifecycle.State.STARTED, - block: suspend CoroutineScope.() -> Unit -) { - require(state !== Lifecycle.State.INITIALIZED) { - "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state." - } - if (this@repeatOnLifecycle.state === Lifecycle.State.DESTROYED) { - return - } - // This scope is required to preserve context before we move to Dispatchers.Main - coroutineScope { - withContext(Dispatchers.Main.immediate) { - // Check the current state of the lifecycle as the previous check is not guaranteed - // to be done on the main thread. - if (this@repeatOnLifecycle.state === Lifecycle.State.DESTROYED) return@withContext - // Instance of the running repeating coroutine - var launchedJob: Job? = null - // Registered observer - var observer: Lifecycle.Callbacks? = null - try { - // Suspend the coroutine until the lifecycle is destroyed or - // the coroutine is cancelled - suspendCancellableCoroutine { cont -> - // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and - // cancels when it falls below that state. - val mutex = Mutex() - observer = object : Lifecycle.Callbacks { - - override fun onCreate() { - if (state == Lifecycle.State.CREATED) { - startWork() - } - } - - override fun onStart() { - if (state == Lifecycle.State.STARTED) { - startWork() - } - } - - override fun onResume() { - if (state == Lifecycle.State.RESUMED) { - startWork() - } - } - - override fun onPause() { - if (state == Lifecycle.State.RESUMED) { - cancelWork() - } - } - - override fun onStop() { - if (state == Lifecycle.State.STARTED) { - cancelWork() - } - } - - override fun onDestroy() { - if (state == Lifecycle.State.CREATED) { - cancelWork() - } - cont.resume(Unit) - } - - private fun startWork() { - // Launch the repeating work preserving the calling context - launchedJob = this@coroutineScope.launch { - // Mutex makes invocations run serially, - // coroutineScope ensures all child coroutines finish - mutex.withLock { - coroutineScope { - block() - } - } - } - } - - private fun cancelWork() { - launchedJob?.cancel() - launchedJob = null - } - }.also { - this@repeatOnLifecycle.subscribe(it) - } - } - } finally { - launchedJob?.cancel() - observer?.let { - this@repeatOnLifecycle.unsubscribe(it) - } - } - } - } -} - -/** - * [LifecycleOwner]'s extension function for [Lifecycle.repeatOnLifecycle] to allow an easier - * call to the API from LifecycleOwners such as Activities and Fragments. - * - * ``` - * class MyActivity : AppCompatActivity() { - * override fun onCreate(savedInstanceState: Bundle?) { - * /* ... */ - * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. - * // The coroutine will be cancelled when the ON_STOP event happens and will - * // restart executing if the lifecycle receives the ON_START event again. - * lifecycleScope.launch { - * repeatOnLifecycle(Lifecycle.State.STARTED) { - * uiStateFlow.collect { uiState -> - * updateUi(uiState) - * } - * } - * } - * } - * } - * ``` - * - * @see Lifecycle.repeatOnLifecycle - */ -suspend fun LifecycleOwner.repeatOnLifecycle( - state: Lifecycle.State = Lifecycle.State.STARTED, - block: suspend CoroutineScope.() -> Unit -): Unit = lifecycle.repeatOnLifecycle(state, block) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/WindowSize.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/WindowSize.kt deleted file mode 100644 index 1a6d2a33..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/lifecycle/WindowSize.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.datlag.burningseries.shared.common.lifecycle - -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass -import androidx.compose.runtime.Composable - -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) -@Composable -fun calculateWindowWidthSize(): WindowSize { - val sizeClass = calculateWindowSizeClass() - - return when (sizeClass.widthSizeClass) { - WindowWidthSizeClass.Medium -> WindowSize.Medium - WindowWidthSizeClass.Expanded -> when (sizeClass.heightSizeClass) { - WindowHeightSizeClass.Compact -> WindowSize.Medium - else -> WindowSize.Expanded - } - else -> WindowSize.Compact - } -} - -sealed interface WindowSize { - data object Compact : WindowSize - data object Medium : WindowSize - data object Expanded : WindowSize -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/DatabaseModule.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/DatabaseModule.kt deleted file mode 100644 index 5c3b723a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/DatabaseModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.datlag.burningseries.shared.module - -import dev.datlag.burningseries.database.BurningSeries -import dev.datlag.burningseries.database.DriverFactory -import org.kodein.di.DI -import org.kodein.di.bindSingleton -import org.kodein.di.instance - -data object DatabaseModule { - - const val NAME = "DatabaseModule" - - val di = DI.Module(NAME) { - import(PlatformModule.di) - - bindSingleton("BurningSeriesDriver") { - instance().createBurningSeriesDriver() - } - bindSingleton { - BurningSeries(instance("BurningSeriesDriver")) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/NetworkModule.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/NetworkModule.kt deleted file mode 100644 index 8103f71f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/NetworkModule.kt +++ /dev/null @@ -1,145 +0,0 @@ -package dev.datlag.burningseries.shared.module - -import de.jensklingenberg.ktorfit.Ktorfit -import de.jensklingenberg.ktorfit.ktorfitBuilder -import dev.datlag.burningseries.database.BurningSeries -import dev.datlag.burningseries.database.common.toGenres -import dev.datlag.burningseries.database.common.toSearchItems -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.datlag.burningseries.network.GitHub -import dev.datlag.burningseries.network.JsonBase -import dev.datlag.burningseries.network.WrapAPI -import dev.datlag.burningseries.network.realm.RealmLoader -import dev.datlag.burningseries.network.state.* -import dev.datlag.burningseries.shared.Sekret -import dev.datlag.burningseries.shared.getPackageName -import dev.datlag.burningseries.shared.other.StateSaver -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseApp -import dev.gitlive.firebase.firestore.firestore -import io.ktor.client.* -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import org.kodein.di.* - -object NetworkModule { - - private const val TAG_KTORFIT_JSONBASE = "JsonBaseKtorfit" - private const val TAG_KTORFIT_WRAPAPI = "WrapAPIKtorfit" - private const val TAG_KTORFIT_FIRESTORE = "FirestoreKtorfit" - private const val TAG_KTORFIT_GITHUB = "GitHubKtorfit" - const val NAME = "NetworkModule" - - val di = DI.Module(NAME) { - import(DatabaseModule.di) - - bindSingleton { - ktorfitBuilder { - httpClient(instance()) - } - } - bindSingleton(TAG_KTORFIT_JSONBASE) { - val builder = instance() - builder.build { - baseUrl("https://jsonbase.com/") - } - } - bindSingleton { - val jsonBaseKtor: Ktorfit = instance(TAG_KTORFIT_JSONBASE) - jsonBaseKtor.create() - } - bindSingleton(TAG_KTORFIT_WRAPAPI) { - val builder = instance() - builder.build { - baseUrl("https://wrapapi.com/use/") - } - } - bindSingleton { - val wrapApiKtor: Ktorfit = instance(TAG_KTORFIT_WRAPAPI) - wrapApiKtor.create() - } - bindSingleton(TAG_KTORFIT_GITHUB) { - val builder = instance() - builder.build { - baseUrl("https://api.github.com/") - } - } - bindSingleton { - val githubApiKtor: Ktorfit = instance(TAG_KTORFIT_GITHUB) - githubApiKtor.create() - } - bindProvider { - HomeStateMachine( - client = instance(), - json = instance(), - wrapApi = instance(), - wrapApiKey = if (StateSaver.sekretLibraryLoaded) { - Sekret().wrapApi(getPackageName()) - } else { null } - ) - } - bindProvider { - val database = instanceOrNull() - - SearchStateMachine( - client = instance(), - json = instance(), - wrapApi = instance(), - wrapApiKey = if (StateSaver.sekretLibraryLoaded) { - Sekret().wrapApi(getPackageName()) - } else { null }, - saveToDB = { - database?.burningSeriesQueries?.transaction { - it.genres.forEach { genre -> - genre.toSearchItems().forEach { item -> - scopeCatching { - database.burningSeriesQueries.insertSearchItem(item) - }.getOrNull() - } - } - } - }, - loadFromDB = { - suspendCatching { - database?.burningSeriesQueries?.selectAllSearchItems()?.executeAsList()?.toGenres() - }.getOrNull() ?: emptyList() - } - ) - } - if (StateSaver.sekretLibraryLoaded) { - bindEagerSingleton { - App.create(instance()) - } - bindEagerSingleton(TAG_KTORFIT_FIRESTORE) { - val builder = instance() - builder.build { - baseUrl("https://firestore.googleapis.com/v1/projects/${Sekret().firebaseProject(getPackageName())!!}/") - } - } - bindEagerSingleton { - val firestoreKtor: Ktorfit = instance(TAG_KTORFIT_FIRESTORE) - firestoreKtor.create() - } - } - bindEagerSingleton { - RealmLoader(instanceOrNull()) - } - bindProvider { - EpisodeStateMachine( - client = instance(), - jsonBase = instance(), - realmLoader = instance(), - firestore = instanceOrNull() ?: instanceOrNull()?.let { Firebase.firestore(it) }, - firestoreApi = instanceOrNull() - ) - } - bindProvider { - SaveStateMachine(instance(), instance(), instance(), instanceOrNull(), instanceOrNull()) - } - bindProvider { - ReleaseStateMachine(instance()) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.kt deleted file mode 100644 index 53da7f59..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.datlag.burningseries.shared.module - -import org.kodein.di.DI - -expect object PlatformModule { - - val di: DI.Module -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Constants.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Constants.kt deleted file mode 100644 index f252b77e..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Constants.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.datlag.burningseries.shared.other - -data object Constants { - data object Sponsor { - const val GITHUB = "https://github.com/sponsors/DatL4g" - const val POLAR = "https://polar.sh/DatL4g" - const val PATREON = "https://patreon.com/datlag" - const val PAYPAL = "https://paypal.me/datlag" - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.kt deleted file mode 100644 index 92378c81..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.datlag.burningseries.shared.other - -import dev.datlag.burningseries.shared.ui.navigation.Component - -expect object Crashlytics { - fun customKey(key: String, value: String) - fun customKey(key: String, value: Boolean) - fun customKey(key: String, value: Int) - fun customKey(key: String, value: Long) - fun customKey(key: String, value: Float) - fun customKey(key: String, value: Double) - fun screen(value: Component) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Project.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Project.kt deleted file mode 100644 index f70fdfaa..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/Project.kt +++ /dev/null @@ -1,35 +0,0 @@ -package dev.datlag.burningseries.shared.other - -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.ImageResource -import dev.icerock.moko.resources.StringResource - -sealed interface Project { - - val icon: ImageResource? - val title: StringResource - val subTitle: StringResource - - val `package`: String - val googlePlay: String? - val github: String? - - data object PulZ : Project { - override val icon: ImageResource = SharedRes.images.PulZ - override val title: StringResource = SharedRes.strings.pulz - override val subTitle: StringResource = SharedRes.strings.pulz_subtitle - - override val `package`: String = "dev.datlag.pulz" - override val googlePlay: String = "https://play.google.com/store/apps/details?id=$`package`" - override val github: String = "https://github.com/DatL4g/PulZ" - } - - data object AniFlow : Project { - override val icon: ImageResource = SharedRes.images.AniFlow - override val title: StringResource = SharedRes.strings.aniflow - override val subTitle: StringResource = SharedRes.strings.aniflow_subtitle - override val `package`: String = "dev.datlag.aniflow" - override val googlePlay: String = "https://play.google.com/store/apps/details?id=$`package`" - override val github: String = "https://github.com/DatL4g/AniFlow" - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/StateSaver.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/StateSaver.kt deleted file mode 100644 index 4c4a1ee7..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/other/StateSaver.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.datlag.burningseries.shared.other - -data object StateSaver { - var homeGridIndex: Int = 0 - var homeGridOffset: Int = 0 - - var seriesListIndex: Int = 0 - var seriesListOffset: Int = 0 - - var sekretLibraryLoaded: Boolean = false -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/AutoSizeText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/AutoSizeText.kt deleted file mode 100644 index 76bad417..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/AutoSizeText.kt +++ /dev/null @@ -1,278 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.BoxWithConstraintsScope -import androidx.compose.foundation.text.InlineTextContent -import androidx.compose.foundation.text.InternalFoundationTextApi -import androidx.compose.foundation.text.TextDelegate -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalFontFamilyResolver -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextLayoutResult -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.* -import dev.datlag.burningseries.model.common.scopeCatching -import kotlin.math.ceil -import kotlin.math.floor -import kotlin.reflect.KProperty - -/** - * Composable function that automatically adjusts the size of text to fit within the given constraints. - * - * Features: - * 1. Best performance: Utilizes a dichotomous binary search algorithm to quickly find the optimal text size without unnecessary iterations. - * 2. Text alignment support: Supports 6 possible alignment values through the Alignment interface. - * 3. Material Design 3 support. - * 4. Font scaling support: Changing the font scale by the user does not affect the visual rendering result. - * - * Limitation: - * 1. Does not work well when maxLine is greater than 1. - * 2. Does not work well when changing lineHeight - * - * @param text The text to be displayed. - * @param modifier The modifier for the text composable. - * @param suggestedFontSizes The suggested font sizes to choose from. - * @param minTextSize The minimum text size allowed. - * @param maxTextSize The maximum text size allowed. - * @param stepGranularityTextSize The step size for adjusting the text size. - * @param textAlignment The alignment of the text within its container. - * @param color The color of the text. - * @param fontStyle The font style of the text. - * @param fontWeight The font weight of the text. - * @param fontFamily The font family of the text. - * @param letterSpacing The letter spacing of the text. - * @param textDecoration The text decoration style. - * @param textAlign The alignment of the text within the lines of the paragraph. - * @param lineHeight The line height of the text. - * @param softWrap Whether the text should break at soft line breaks. - * @param maxLines The maximum number of lines for the text. - * @param minLines The minimum number of lines for the text. - * @param onTextLayout Callback invoked when the text layout is available. - * @param style The base style to apply to the text. - * @author Reda El Madini - For support, contact gladiatorkilo@gmail.com - */ -@Composable -fun AutoSizeText( - text: String, - modifier: Modifier = Modifier, - suggestedFontSizes: ImmutableWrapper> = emptyList().toImmutableWrapper(), - minTextSize: TextUnit = TextUnit.Unspecified, - maxTextSize: TextUnit = TextUnit.Unspecified, - stepGranularityTextSize: TextUnit = TextUnit.Unspecified, - textAlignment: Alignment = Alignment.Center, - color: Color = Color.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - softWrap: Boolean = true, - maxLines: Int = 1, - minLines: Int = 1, - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current, -) { - AutoSizeText( - text = AnnotatedString(text), - modifier = modifier, - suggestedFontSizes = suggestedFontSizes, - minTextSize = minTextSize, - maxTextSize = maxTextSize, - stepGranularityTextSize = stepGranularityTextSize, - textAlignment = textAlignment, - color = color, - fontStyle = fontStyle, - fontWeight = fontWeight, - fontFamily = fontFamily, - letterSpacing = letterSpacing, - textDecoration = textDecoration, - textAlign = textAlign, - lineHeight = lineHeight, - softWrap = softWrap, - maxLines = maxLines, - minLines = minLines, - onTextLayout = onTextLayout, - style = style, - ) -} - -@Composable -fun AutoSizeText( - text: AnnotatedString, - modifier: Modifier = Modifier, - suggestedFontSizes: ImmutableWrapper> = emptyList().toImmutableWrapper(), - minTextSize: TextUnit = TextUnit.Unspecified, - maxTextSize: TextUnit = TextUnit.Unspecified, - stepGranularityTextSize: TextUnit = TextUnit.Unspecified, - textAlignment: Alignment = Alignment.Center, - color: Color = Color.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - softWrap: Boolean = true, - maxLines: Int = 1, - minLines: Int = 1, - inlineContent: ImmutableWrapper> = mapOf().toImmutableWrapper(), - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current, -) { - val permittedTextUnitTypes = remember { listOf(TextUnitType.Unspecified, TextUnitType.Sp) } - check(minTextSize.type in permittedTextUnitTypes) - check(maxTextSize.type in permittedTextUnitTypes) - check(stepGranularityTextSize.type in permittedTextUnitTypes) - - val density = LocalDensity.current.density - // Change font scale to 1 - CompositionLocalProvider(LocalDensity provides Density(density = density, fontScale = 1F)) { - BoxWithConstraints( - modifier = modifier, - contentAlignment = textAlignment, - ) { - // (1 / density).sp represents 1px when font scale equals 1 - val step = remember(stepGranularityTextSize) { - (1 / density).let { - if (stepGranularityTextSize.isUnspecified) - it.sp - else - stepGranularityTextSize.value.coerceAtLeast(it).sp - } - } - - val max = remember(maxWidth, maxHeight, maxTextSize) { - min(maxWidth, maxHeight).value.let { - if (maxTextSize.isUnspecified) - it.sp - else - maxTextSize.value.coerceAtMost(it).sp - } - } - - val min = remember(minTextSize, step, max) { - if (minTextSize.isUnspecified) { - step - } else { - (scopeCatching { - minTextSize.value.coerceIn( - minimumValue = step.value, - maximumValue = max.value - ) - }.getOrNull() ?: max.value).sp - } - } - - val possibleFontSizes = remember(suggestedFontSizes, min, max, step) { - if (suggestedFontSizes.value.isEmpty()) { - val firstIndex = ceil(min.value / step.value).toInt() - val lastIndex = floor(max.value / step.value).toInt() - MutableList(size = (lastIndex - firstIndex) + 1) { index -> - step * (lastIndex - index) - } - } else - suggestedFontSizes.value.filter { - it.isSp && it.value in min.value..max.value - }.sortedByDescending { - it.value - } - } - - var combinedTextStyle = LocalTextStyle.current + style - - if (possibleFontSizes.isNotEmpty()) { - // Dichotomous binary search - var low = 0 - var high = possibleFontSizes.lastIndex - while (low <= high) { - val mid = low + (high - low) / 2 - val shouldShrink = shouldShrink( - text = text, - textStyle = combinedTextStyle.copy(fontSize = possibleFontSizes[mid]), - maxLines = maxLines, - softWrap = softWrap, - ) - - if (shouldShrink) low = mid + 1 - else high = mid - 1 - } - combinedTextStyle = combinedTextStyle.copy( - fontSize = possibleFontSizes[low.coerceIn(possibleFontSizes.indices)] - ) - } - - Text( - text = text, - modifier = Modifier, - color = color, - fontSize = TextUnit.Unspecified, - fontStyle = fontStyle, - fontWeight = fontWeight, - fontFamily = fontFamily, - letterSpacing = letterSpacing, - textDecoration = textDecoration, - textAlign = textAlign, - lineHeight = lineHeight, - overflow = TextOverflow.Ellipsis, - softWrap = softWrap, - maxLines = maxLines, - minLines = minLines, - inlineContent = inlineContent.value, - onTextLayout = onTextLayout, - style = combinedTextStyle, - ) - } - } -} - -@OptIn(InternalFoundationTextApi::class) -@Composable -private fun BoxWithConstraintsScope.shouldShrink( - text: AnnotatedString, - textStyle: TextStyle, - maxLines: Int, - softWrap: Boolean, -): Boolean { - val textDelegate = TextDelegate( - text = text, - style = textStyle, - maxLines = maxLines, - softWrap = softWrap, - overflow = TextOverflow.Clip, - density = LocalDensity.current, - fontFamilyResolver = LocalFontFamilyResolver.current, - ) - - val textLayoutResult = textDelegate.layout( - constraints, - LocalLayoutDirection.current, - ) - - return textLayoutResult.hasVisualOverflow -} - -@Immutable -data class ImmutableWrapper(val value: T) - -fun T.toImmutableWrapper() = ImmutableWrapper(this) - -operator fun ImmutableWrapper.getValue(thisRef: Any?, property: KProperty<*>) = value \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/CountryImage.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/CountryImage.kt deleted file mode 100644 index e43c8ab5..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/CountryImage.kt +++ /dev/null @@ -1,64 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.Image -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.ifTrue -import dev.icerock.moko.resources.compose.painterResource - -@Composable -fun CountryImage( - code: String, - description: String?, - iconSize: Dp? = null, -) { - val res = remember(code) { dev.datlag.burningseries.shared.ui.theme.CountryImage.getByFlag(code) } - var iconWidth by remember { mutableFloatStateOf(iconSize?.value ?: 24.dp.value) } - var iconHeight by remember { mutableFloatStateOf(iconSize?.value ?: 24.dp.value) } - - Box( - modifier = Modifier.ifTrue(iconSize == null) { - this.onSizeChanged { - val thirdWidth = it.width.toFloat() - (it.width.toFloat() / 2F) - val thirdHeight = it.height.toFloat() - (it.height.toFloat() / 2F) - iconWidth = thirdWidth - iconHeight = thirdHeight - } - }.ifTrue(res.size >= 2) { - size(width = (iconWidth * 1.5).dp, height = (iconHeight * 1.5).dp) - }, - contentAlignment = Alignment.Center - ) { - Image( - painter = painterResource(res.last()), - contentDescription = description, - modifier = Modifier - .size(width = iconWidth.dp, height = iconHeight.dp) - .clip(MaterialTheme.shapes.extraSmall) - .border(1.dp, LocalContentColor.current, MaterialTheme.shapes.extraSmall) - .ifTrue(res.size >= 2) { this.align(Alignment.TopStart).alpha(0.75F) } - ) - if (res.size >= 2) { - Image( - painter = painterResource(res.first()), - contentDescription = description, - modifier = Modifier - .size(width = iconWidth.dp, height = iconHeight.dp) - .clip(MaterialTheme.shapes.extraSmall) - .border(1.dp, LocalContentColor.current, MaterialTheme.shapes.extraSmall) - .align(Alignment.BottomEnd) - ) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Cover.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Cover.kt deleted file mode 100644 index 689dfb89..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/Cover.kt +++ /dev/null @@ -1,62 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.NoPhotography -import androidx.compose.material3.LocalContentColor -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.layout.ContentScale -import coil3.ImageLoader -import coil3.PlatformContext -import coil3.compose.AsyncImage -import coil3.compose.AsyncImagePainter -import coil3.request.ImageRequest -import dev.datlag.burningseries.shared.LocalDI -import org.kodein.di.instance - -@Composable -fun Cover( - key: String?, - data: Any?, - contentDescription: String?, - contentScale: ContentScale = ContentScale.FillWidth, - modifier: Modifier = Modifier, - errorPainter: Painter = rememberVectorPainter(Icons.Default.NoPhotography), - errorColorFilter: ColorFilter? = ColorFilter.tint(LocalContentColor.current), - errorScale: ContentScale = ContentScale.Inside, - onSuccess: (AsyncImagePainter.State.Success) -> Unit = {} -) { - val platformContext: PlatformContext by LocalDI.current.instance() - val imageLoader: ImageLoader by LocalDI.current.instance() - var scale by remember { mutableStateOf(contentScale) } - var filter by remember { mutableStateOf(null) } - - AsyncImage( - model = ImageRequest.Builder(platformContext) - .data(data) - .placeholderMemoryCacheKey(key) - .build(), - error = errorPainter, - imageLoader = imageLoader, - contentDescription = contentDescription, - contentScale = scale, - modifier = modifier, - onLoading = { - scale = contentScale - filter = null - }, - onError = { - scale = errorScale - filter = errorColorFilter - }, - onSuccess = { - scale = contentScale - filter = null - onSuccess(it) - }, - colorFilter = filter - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt deleted file mode 100644 index 62446613..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt +++ /dev/null @@ -1,321 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.text.InlineTextContent -import androidx.compose.foundation.text.appendInlineContent -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.ResolvedTextDirection -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.sp -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.shared.common.safeSubSequence -import kotlin.math.max - - -/** - * @author dokar3 - * https://github.com/dokar3/ExpandableText - */ - -private const val INLINE_CONTENT_ID = "EXPANDABLE_TEXT_TOGGLE" - -private data class ToggleSize( - val width: Int = 0, - val widthSp: TextUnit = 0.sp, - val height: Int = 0, - val heightSp: TextUnit = 0.sp -) - -private data class ExpandableTextInfo( - val visibleCharCount: Int, - val shouldShowToggleContent: Boolean, -) - -/** - * Display an expandable text, require `maxLines` to make text expandable. - * - * @param expanded Controls the expanded state of text. - * @param text Text to display. - * @param collapsedMaxLines The max lines when [expanded] is false. - * @param expandedMaxLines The max lines when [expanded] is true. Defaults to [Int.MAX_VALUE]. - * @param toggle The toggle displayed at end of the text if text can not be fully displayed. - * @see [Text] - */ -@Composable -fun ExpandableText( - expanded: Boolean, - text: String, - collapsedMaxLines: Int, - modifier: Modifier = Modifier, - expandedMaxLines: Int = Int.MAX_VALUE, - toggle: @Composable (() -> Unit)? = null, - color: Color = Color.Unspecified, - fontSize: TextUnit = TextUnit.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - overflow: TextOverflow = TextOverflow.Clip, - softWrap: Boolean = true, - inlineContent: Map = mapOf(), - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current -) { - val annotatedString = remember(text) { AnnotatedString(text) } - ExpandableText( - expanded = expanded, - text = annotatedString, - modifier = modifier, - toggle = toggle, - color = color, - fontSize = fontSize, - fontStyle = fontStyle, - fontWeight = fontWeight, - fontFamily = fontFamily, - letterSpacing = letterSpacing, - textDecoration = textDecoration, - textAlign = textAlign, - lineHeight = lineHeight, - overflow = overflow, - softWrap = softWrap, - collapsedMaxLines = collapsedMaxLines, - expandedMaxLines = expandedMaxLines, - inlineContent = inlineContent, - onTextLayout = onTextLayout, - style = style - ) -} - -/** - * Display an expandable text, require `maxLines` to make text expandable. - * - * @param expanded Controls the expanded state of text. - * @param text Text to display. - * @param collapsedMaxLines The max lines when [expanded] is false. - * @param expandedMaxLines The max lines when [expanded] is true. Defaults to [Int.MAX_VALUE]. - * @param toggle The toggle displayed at end of the text if text can not be fully displayed. - * @see [Text] - */ -@Composable -fun ExpandableText( - expanded: Boolean, - text: AnnotatedString, - collapsedMaxLines: Int, - modifier: Modifier = Modifier, - expandedMaxLines: Int = Int.MAX_VALUE, - toggle: @Composable (() -> Unit)? = null, - color: Color = Color.Unspecified, - fontSize: TextUnit = TextUnit.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - overflow: TextOverflow = TextOverflow.Clip, - softWrap: Boolean = true, - inlineContent: Map = mapOf(), - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current -) { - var textInfo by remember(text) { - mutableStateOf( - ExpandableTextInfo( - visibleCharCount = text.length, - shouldShowToggleContent = false, - ) - ) - } - - val expandableText = remember(text, toggle as Any?, textInfo) { - if (textInfo.shouldShowToggleContent && toggle != null) { - buildAnnotatedString { - append(text.safeSubSequence(0, textInfo.visibleCharCount)) - appendInlineContent(INLINE_CONTENT_ID) - } - } else { - text - } - } - - val layoutResult = remember { mutableStateOf(null) } - - val toggleSize = measureToggle(toggle) - - val expandableInlineContent = remember( - inlineContent, - toggle as Any?, - textInfo, - toggleSize, - ) { - if (textInfo.shouldShowToggleContent && toggle != null) { - val content = InlineTextContent( - placeholder = Placeholder( - width = toggleSize.widthSp, - height = toggleSize.heightSp, - placeholderVerticalAlign = PlaceholderVerticalAlign.Center, - ), - children = { toggle() } - ) - inlineContent + Pair(INLINE_CONTENT_ID, content) - } else { - inlineContent - } - } - - fun tryUpdateTextInfo( - toggleSize: ToggleSize, - layoutRet: TextLayoutResult, - ) { - if (toggleSize.width == 0) return - val actualMaxLines = if (expanded) expandedMaxLines else collapsedMaxLines - if (layoutRet.lineCount == actualMaxLines) { - val lineEnd = layoutRet.getLineEnd(layoutRet.lineCount - 1) - if (lineEnd == expandableText.length) { - // Text is fully displayed - val visibleChars = if (textInfo.shouldShowToggleContent) { - expandableText.length - 1 - } else { - expandableText.length - } - textInfo = textInfo.copy(visibleCharCount = visibleChars) - return - } - val lineTop = layoutRet.getLineTop(layoutRet.lineCount - 1) - val isLtr = try { - layoutRet.getParagraphDirection(lineEnd) == ResolvedTextDirection.Ltr - } catch (e: ArrayIndexOutOfBoundsException) { - // Error occurred in MultiParagraph.getParagraphDirection() - true - } - val visibleChars = if (isLtr) { - val toggleTopLeft = Offset( - x = layoutRet.size.width - toggleSize.width.toFloat(), - y = lineTop + toggleSize.height / 2f, - ) - var count = layoutRet.getOffsetForPosition(toggleTopLeft) - while (count > 0) { - val charRight = layoutRet.getBoundingBox(offset = count - 1).right - val isOverlapped = charRight >= toggleTopLeft.x - val isWhitespace = scopeCatching { - text[count - 1].isWhitespace() - }.getOrNull() ?: scopeCatching { - text.lastOrNull()?.isWhitespace() - }.getOrNull() ?: false - if (isOverlapped || isWhitespace) { - count-- - } else { - break - } - } - count - } else { - val toggleTopRight = Offset( - x = toggleSize.width.toFloat(), - y = lineTop + toggleSize.height / 2f, - ) - var count = layoutRet.getOffsetForPosition(toggleTopRight) - while (count > 0) { - val charLeft = layoutRet.getBoundingBox(offset = count - 1).left - val isOverlapped = charLeft <= toggleTopRight.x - val isWhitespace = scopeCatching { - text[count - 1].isWhitespace() - }.getOrNull() ?: scopeCatching { - text.lastOrNull()?.isWhitespace() - }.getOrNull() ?: false - if (isOverlapped || isWhitespace) { - count-- - } else { - break - } - } - count - } - textInfo = textInfo.copy( - visibleCharCount = visibleChars, - shouldShowToggleContent = true, - ) - } else { - textInfo = textInfo.copy(visibleCharCount = text.length) - } - } - - LaunchedEffect( - expanded, - collapsedMaxLines, - expandedMaxLines, - toggleSize, - layoutResult.value, - ) { - val layoutRet = layoutResult.value ?: return@LaunchedEffect - if (toggleSize.width > 0) { - tryUpdateTextInfo(toggleSize, layoutRet) - } - } - - Text( - text = expandableText, - modifier = modifier, - color = color, - fontSize = fontSize, - fontStyle = fontStyle, - fontWeight = fontWeight, - fontFamily = fontFamily, - letterSpacing = letterSpacing, - textDecoration = textDecoration, - textAlign = textAlign, - lineHeight = lineHeight, - overflow = overflow, - softWrap = softWrap, - maxLines = if (expanded) expandedMaxLines else collapsedMaxLines, - inlineContent = expandableInlineContent, - onTextLayout = { - onTextLayout(it) - layoutResult.value = it - }, - style = style - ) -} - -@Composable -private fun measureToggle( - content: @Composable (() -> Unit)?, -): ToggleSize { - var size by remember(content as Any?) { mutableStateOf(ToggleSize()) } - if (content != null) { - Layout(content = content) { measurables, constraints -> - var maxWidth = 0 - var maxHeight = 0 - measurables.map { - it.measure(constraints) - }.forEach { - maxWidth = max(maxWidth, it.measuredWidth) - maxHeight = max(maxHeight, it.measuredHeight) - } - size = ToggleSize( - width = maxWidth, - widthSp = maxWidth.toSp(), - height = maxHeight, - heightSp = maxHeight.toSp(), - ) - layout(0, 0) {} - } - } - return size -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandedPages.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandedPages.kt deleted file mode 100644 index 0cdacb3d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandedPages.kt +++ /dev/null @@ -1,35 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.runtime.Composable -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.router.pages.ChildPages -import com.arkivanov.decompose.value.Value - -@OptIn(ExperimentalDecomposeApi::class) -@Composable -fun ExpandedPages( - pages: Value>, - pageContent: @Composable (index: Int, page: T) -> Unit -) { - val state = pages.subscribeAsState() - - ExpandedPages( - pages = state.value, - pageContent = pageContent - ) -} - -@OptIn(ExperimentalDecomposeApi::class) -@Composable -fun ExpandedPages( - pages: ChildPages<*, T>, - pageContent: @Composable (index: Int, page: T) -> Unit -) { - val selectedIndex = pages.selectedIndex - val items = pages.items - - items[selectedIndex].instance?.also { page -> - pageContent(selectedIndex, page) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/FloatingSearchButton.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/FloatingSearchButton.kt deleted file mode 100644 index 4bab4f7b..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/FloatingSearchButton.kt +++ /dev/null @@ -1,181 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.TextSelectionColors -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material.icons.filled.Search -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.withIOContext -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.delay - -@Composable -fun FloatingSearchButton( - icon: ImageVector = Icons.Default.Search, - contentDescription: String? = stringResource(SharedRes.strings.search), - enabled: Boolean = true, - clearIcon: ImageVector = Icons.Default.Clear, - closeIcon: ImageVector = Icons.AutoMirrored.Default.KeyboardArrowRight, - modifier: Modifier = Modifier, - onClick: () -> Unit = { }, - overrideOnClick: Boolean = false, - onTextChange: (String) -> Unit -) { - val focusRequester = remember { FocusRequester() } - var opened by remember { mutableStateOf(false) } - val textState = remember { mutableStateOf("") } - val enabledColor = if (enabled) { - MaterialTheme.colorScheme.primaryContainer - } else { - MaterialTheme.colorScheme.tertiaryContainer - } - val animatedColor by animateColorAsState( - targetValue = enabledColor, - animationSpec = tween() - ) - - Surface( - color = animatedColor, - modifier = modifier, - shape = FloatingActionButtonDefaults.shape, - shadowElevation = 6.dp, - onClick = { - if (overrideOnClick) { - onClick() - } else { - if (!opened) { - opened = true - } else { - focusRequester.requestFocus() - } - } - }, - enabled = enabled - ) { - AnimatedContent(targetState = opened) { expand -> - if (expand) { - SearchBar( - close = { - opened = false - }, - clearIcon = clearIcon, - closeIcon = closeIcon, - focusRequester = focusRequester, - textState = textState, - onTextChange = onTextChange - ) - - LaunchedEffect(focusRequester) { - withIOContext { - delay(500) // wait till transition is done - } - focusRequester.requestFocus() - } - } else { - Box( - modifier = Modifier.size(56.dp), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = icon, - contentDescription = contentDescription - ) - } - } - } - } -} - -@Composable -private fun SearchBar( - focusRequester: FocusRequester, - textState: MutableState, - close: () -> Unit, - clearIcon: ImageVector, - closeIcon: ImageVector, - onTextChange: (String) -> Unit -) { - TextField( - value = textState.value, - onValueChange = { - textState.value = it - onTextChange(textState.value) - }, - modifier = Modifier.focusRequester(focusRequester), - placeholder = { - Text( - text = stringResource(SharedRes.strings.search), - style = MaterialTheme.typography.labelLarge - ) - }, - singleLine = true, - colors = searchTextFieldColors(), - keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search), - leadingIcon = { - IconButton( - onClick = close - ) { - Icon( - imageVector = closeIcon, - contentDescription = stringResource(SharedRes.strings.close) - ) - } - }, - trailingIcon = { - IconButton( - onClick = { - textState.value = "" - onTextChange(textState.value) - } - ) { - Icon( - imageVector = clearIcon, - contentDescription = stringResource(SharedRes.strings.clear) - ) - } - } - ) -} - -@Composable -private fun searchTextFieldColors( - contentColor: Color = LocalContentColor.current -): TextFieldColors { - return TextFieldDefaults.colors( - unfocusedTextColor = contentColor, - focusedTextColor = contentColor, - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - disabledContainerColor = Color.Transparent, - cursorColor = contentColor, - selectionColors = TextSelectionColors( - handleColor = contentColor, - backgroundColor = contentColor.copy(alpha = 0.3f) - ), - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedLeadingIconColor = contentColor, - unfocusedLeadingIconColor = contentColor, - focusedTrailingIconColor = contentColor, - unfocusedTrailingIconColor = contentColor, - unfocusedPlaceholderColor = contentColor.copy(alpha = 0.5F), - focusedPlaceholderColor = contentColor.copy(alpha = 0.5F), - ) -} diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.kt deleted file mode 100644 index 86bea053..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import dev.datlag.burningseries.shared.other.Project - -@Composable -expect fun ProjectCard(project: Project, modifier: Modifier = Modifier) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.kt deleted file mode 100644 index 1f384006..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -expect fun VerticalScrollbar(adapter: ScrollbarAdapter, modifier: Modifier = Modifier) - -@Composable -expect fun rememberScrollbarAdapter( - scrollState: LazyGridState, -): ScrollbarAdapter - -@Composable -expect fun rememberScrollbarAdapter( - scrollState: LazyListState, -): ScrollbarAdapter - -expect interface ScrollbarAdapter { - val scrollOffset: Double - val contentSize: Double - val viewportSize: Double - suspend fun scrollTo(scrollOffset: Double) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/BrowserState.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/BrowserState.kt deleted file mode 100644 index 1fb0be0a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/BrowserState.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.state - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun BrowserState(text: String) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val painterRes = if (LocalDarkMode.current) { - SharedRes.images.browser_dark - } else { - SharedRes.images.browser_light - } - Image( - modifier = Modifier.fillMaxWidth(0.7F), - painter = painterResource(painterRes), - contentDescription = text - ) - Text( - modifier = Modifier.fillMaxWidth(0.85F), - text = text, - fontWeight = FontWeight.SemiBold, - softWrap = true, - textAlign = TextAlign.Center - ) - } -} - -@Composable -fun BrowserState(text: StringResource) = BrowserState(text = stringResource(text)) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/ErrorState.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/ErrorState.kt deleted file mode 100644 index a86e982c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/ErrorState.kt +++ /dev/null @@ -1,62 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.state - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun ErrorState(text: String, customText: (@Composable () -> Unit)? = null, onRetry: () -> Unit) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val painterRes = if (LocalDarkMode.current) { - SharedRes.images.sad_dark - } else { - SharedRes.images.sad_light - } - Image( - modifier = Modifier.fillMaxWidth(0.7F), - painter = painterResource(painterRes), - contentDescription = text - ) - Text( - modifier = Modifier.fillMaxWidth(0.85F), - text = text, - fontWeight = FontWeight.SemiBold, - softWrap = true, - textAlign = TextAlign.Center - ) - customText?.invoke() - Button( - onClick = { - onRetry() - } - ) { - Text(text = stringResource(SharedRes.strings.retry)) - } - } -} - -@Composable -fun ErrorState( - text: StringResource, - customText: (@Composable () -> Unit)? = null, - onRetry: () -> Unit -) = ErrorState(text = stringResource(text), customText, onRetry) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/LoadingState.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/LoadingState.kt deleted file mode 100644 index ed8a7da2..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/LoadingState.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.state - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun LoadingState(text: String) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val painterRes = if (LocalDarkMode.current) { - SharedRes.images.movie_dark - } else { - SharedRes.images.movie_light - } - Image( - modifier = Modifier.fillMaxWidth(0.7F), - painter = painterResource(painterRes), - contentDescription = text - ) - Text( - modifier = Modifier.fillMaxWidth(0.85F), - text = text, - fontWeight = FontWeight.SemiBold, - softWrap = true, - textAlign = TextAlign.Center - ) - } -} - -@Composable -fun LoadingState(text: StringResource) = LoadingState(text = stringResource(text)) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/UnreachableState.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/UnreachableState.kt deleted file mode 100644 index 273ed2ea..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/state/UnreachableState.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.state - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.StringResource -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun UnreachableState(text: String) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val painterRes = if (LocalDarkMode.current) { - SharedRes.images.server_dark - } else { - SharedRes.images.server_light - } - Image( - modifier = Modifier.fillMaxWidth(0.7F), - painter = painterResource(painterRes), - contentDescription = text - ) - Text( - modifier = Modifier.fillMaxWidth(0.85F), - text = text, - fontWeight = FontWeight.SemiBold, - softWrap = true, - textAlign = TextAlign.Center - ) - } -} - -@Composable -fun UnreachableState(text: StringResource) = UnreachableState(text = stringResource(text)) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/Component.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/Component.kt deleted file mode 100644 index 1a62445d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/Component.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.datlag.burningseries.shared.ui.navigation - -import androidx.compose.runtime.Composable -import org.kodein.di.DIAware - -interface Component : DIAware { - - @Composable - fun render() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/DialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/DialogComponent.kt deleted file mode 100644 index f7dad3fb..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/DialogComponent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.datlag.burningseries.shared.ui.navigation - -interface DialogComponent : Component { - fun dismiss() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/NavHostComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/NavHostComponent.kt deleted file mode 100644 index faa443d2..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/NavHostComponent.kt +++ /dev/null @@ -1,85 +0,0 @@ -package dev.datlag.burningseries.shared.ui.navigation - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.stack.Children -import com.arkivanov.decompose.router.stack.StackNavigation -import com.arkivanov.decompose.router.stack.childStack -import com.arkivanov.decompose.router.stack.pop -import com.arkivanov.decompose.router.stack.push -import dev.datlag.burningseries.model.Shortcut -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.common.backAnimation -import dev.datlag.burningseries.shared.ui.screen.initial.InitialScreenComponent -import dev.datlag.burningseries.shared.ui.screen.video.VideoScreenComponent -import org.kodein.di.DI - -class NavHostComponent( - componentContext: ComponentContext, - override val di: DI, - shortcutIntent: Shortcut.Intent = Shortcut.Intent.NONE -) : Component, ComponentContext by componentContext { - - private val navigation = StackNavigation() - private val stack = childStack( - source = navigation, - serializer = ScreenConfig.serializer(), - initialConfiguration = ScreenConfig.Home( - shortcutIntent = shortcutIntent - ), - childFactory = ::createScreenComponent - ) - - private fun createScreenComponent( - screenConfig: ScreenConfig, - componentContext: ComponentContext - ) : Component { - return when (screenConfig) { - is ScreenConfig.Home -> InitialScreenComponent( - componentContext = componentContext, - di = di, - shortcutIntent = screenConfig.shortcutIntent, - watchVideo = { schemeKey, series, episode, stream -> - navigation.push( - ScreenConfig.Video( - schemeKey, - series, - episode, - stream.toList() - ) - ) - }, - onBack = navigation::pop - ) - is ScreenConfig.Video -> VideoScreenComponent( - componentContext = componentContext, - di = di, - schemeKey = screenConfig.schemeKey, - series = screenConfig.series, - initialEpisode = screenConfig.episode, - initialStreams = screenConfig.streams, - onBack = navigation::pop - ) - } - } - - @Composable - override fun render() { - Children( - stack = stack, - animation = backAnimation( - backHandler = this.backHandler, - onBack = { - navigation.pop() - } - ) - ) { - CompositionLocalProvider( - LocalDI provides di - ) { - it.instance.render() - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/ScreenConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/ScreenConfig.kt deleted file mode 100644 index 57cb49e2..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/navigation/ScreenConfig.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.datlag.burningseries.shared.ui.navigation - -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.Shortcut -import dev.datlag.skeo.Stream -import kotlinx.serialization.Serializable - -@Serializable -sealed class ScreenConfig { - - @Serializable - data class Home( - val shortcutIntent: Shortcut.Intent - ) : ScreenConfig() - - @Serializable - data class Video( - val schemeKey: String, - val series: Series, - val episode: Series.Episode, - val streams: List - ) : ScreenConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialComponent.kt deleted file mode 100644 index 236a2313..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialComponent.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial - -import androidx.compose.ui.graphics.vector.ImageVector -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.router.pages.ChildPages -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.icerock.moko.resources.StringResource -import kotlinx.coroutines.flow.StateFlow - -interface InitialComponent : Component { - - val pagerItems: List - val selectedPage: Value - - @OptIn(ExperimentalDecomposeApi::class) - val pages: Value> - - val sponsorScrollEnabled: StateFlow - val homeScrollEnabled: StateFlow - val favoriteScrollEnabled: StateFlow - - fun selectPage(index: Int) - - data class PagerItem( - val label: StringResource, - val unselectedIcon: ImageVector, - val selectedIcon: ImageVector - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreen.kt deleted file mode 100644 index 7b365c74..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreen.kt +++ /dev/null @@ -1,235 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.pages.Pages -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeChild -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import dev.chrisbanes.haze.materials.HazeMaterials -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.LocalPaddingValues -import dev.datlag.burningseries.shared.common.lifecycle.WindowSize -import dev.datlag.burningseries.shared.common.lifecycle.calculateWindowWidthSize -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.rememberIsTv -import dev.datlag.burningseries.shared.ui.custom.ExpandedPages -import dev.icerock.moko.resources.compose.stringResource - -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) -@Composable -fun InitialScreen(component: InitialComponent) { - val haze = remember { HazeState() } - - CompositionLocalProvider( - LocalHaze provides haze - ) { - Box( - modifier = Modifier.fillMaxSize() - ) { - when (calculateWindowWidthSize()) { - is WindowSize.Compact -> CompactScreen(component) - is WindowSize.Medium -> MediumScreen(component) - is WindowSize.Expanded -> { - if (rememberIsTv()) { - MediumScreen(component) - } else { - ExpandedScreen(component) - } - } - } - } - } -} - -@OptIn(ExperimentalDecomposeApi::class, ExperimentalFoundationApi::class, ExperimentalHazeMaterialsApi::class) -@Composable -private fun CompactScreen( - component: InitialComponent -) { - val selectedPage by component.selectedPage.subscribeAsState() - - Scaffold( - bottomBar = { - NavigationBar( - modifier = Modifier.hazeChild( - state = LocalHaze.current, - style = HazeMaterials.thin(NavigationBarDefaults.containerColor) - ).fillMaxWidth(), - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.contentColorFor(NavigationBarDefaults.containerColor) - ) { - component.pagerItems.forEachIndexed { index, item -> - NavigationBarItem( - selected = selectedPage == index, - icon = { - NavIcon( - selected = selectedPage == index, - item = item - ) - }, - onClick = { - component.selectPage(index) - }, - label = { - Text(text = stringResource(item.label)) - }, - alwaysShowLabel = true - ) - } - } - } - ) { - CompositionLocalProvider( - LocalPaddingValues provides it - ) { - Box(modifier = Modifier.fillMaxSize()) { - val sponsorScrollEnabled by component.sponsorScrollEnabled.collectAsStateWithLifecycle() - val homeScrollEnabled by component.homeScrollEnabled.collectAsStateWithLifecycle() - val favoriteScrollEnabled by component.favoriteScrollEnabled.collectAsStateWithLifecycle() - - Pages( - pages = component.pages, - onPageSelected = { index -> - if (selectedPage != index) { - component.selectPage(index) - } - }, - pager = { modifier, state, key, pageContent -> - val scrollEnabled = when (state.currentPage) { - 0 -> sponsorScrollEnabled - 1 -> homeScrollEnabled - 2 -> favoriteScrollEnabled - else -> sponsorScrollEnabled && homeScrollEnabled && favoriteScrollEnabled - } - HorizontalPager( - modifier = modifier, - state = state, - key = key, - pageContent = pageContent, - userScrollEnabled = scrollEnabled - ) - } - ) { _, page -> - page.render() - } - } - } - } -} - -@OptIn(ExperimentalDecomposeApi::class) -@Composable -private fun MediumScreen( - component: InitialComponent -) { - val selectedPage by component.selectedPage.subscribeAsState() - - Scaffold { - Row(modifier = Modifier.padding(it)) { - NavigationRail { - Spacer(modifier = Modifier.weight(1F)) - component.pagerItems.forEachIndexed { index, item -> - NavigationRailItem( - selected = selectedPage == index, - icon = { - NavIcon( - selected = selectedPage == index, - item = item - ) - }, - onClick = { - component.selectPage(index) - }, - label = { - Text(text = stringResource(item.label)) - }, - alwaysShowLabel = true - ) - } - Spacer(modifier = Modifier.weight(1F)) - } - - ExpandedPages( - pages = component.pages - ) { _, page -> - page.render() - } - } - } -} - -@OptIn(ExperimentalDecomposeApi::class) -@Composable -private fun ExpandedScreen( - component: InitialComponent -) { - val selectedPage by component.selectedPage.subscribeAsState() - - Scaffold { - PermanentNavigationDrawer( - modifier = Modifier.padding(it), - drawerContent = { - PermanentDrawerSheet( - drawerShape = RoundedCornerShape( - topStart = 0.dp, - topEnd = 16.dp, - bottomEnd = 16.dp, - bottomStart = 0.dp - ) - ) { - Spacer(modifier = Modifier.weight(1F)) - component.pagerItems.forEachIndexed { index, item -> - NavigationDrawerItem( - selected = selectedPage == index, - icon = { - NavIcon( - selected = selectedPage == index, - item = item - ) - }, - onClick = { - component.selectPage(index) - }, - label = { - Text(text = stringResource(item.label)) - } - ) - } - Spacer(modifier = Modifier.weight(1F)) - } - } - ) { - ExpandedPages( - pages = component.pages - ) { _, page -> - page.render() - } - } - } -} - -@Composable -private fun NavIcon( - selected: Boolean, - item: InitialComponent.PagerItem -) { - Icon( - imageVector = if (selected) item.selectedIcon else item.unselectedIcon, - contentDescription = stringResource(item.label), - modifier = Modifier.size(24.dp) - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreenComponent.kt deleted file mode 100644 index 449070ec..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/InitialScreenComponent.kt +++ /dev/null @@ -1,149 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Savings -import androidx.compose.material.icons.outlined.FavoriteBorder -import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Savings -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.router.pages.* -import com.arkivanov.decompose.value.Value -import com.arkivanov.decompose.value.operator.map -import com.arkivanov.essenty.backhandler.BackCallback -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.Shortcut -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.screen.initial.favorite.FavoriteScreenComponent -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeScreenComponent -import dev.datlag.burningseries.shared.ui.screen.initial.sponsor.SponsorScreenComponent -import dev.datlag.skeo.Stream -import kotlinx.coroutines.flow.MutableStateFlow -import org.kodein.di.DI - -class InitialScreenComponent( - componentContext: ComponentContext, - override val di: DI, - shortcutIntent: Shortcut.Intent, - private val watchVideo: (String, Series, Series.Episode, Collection) -> Unit, - private val onBack: () -> Unit -) : InitialComponent, ComponentContext by componentContext { - - override val pagerItems: List = listOf( - InitialComponent.PagerItem( - label = SharedRes.strings.sponsor, - unselectedIcon = Icons.Outlined.Savings, - selectedIcon = Icons.Filled.Savings - ), - InitialComponent.PagerItem( - label = SharedRes.strings.home, - unselectedIcon = Icons.Outlined.Home, - selectedIcon = Icons.Filled.Home - ), - InitialComponent.PagerItem( - label = SharedRes.strings.favorites, - unselectedIcon = Icons.Outlined.FavoriteBorder, - selectedIcon = Icons.Filled.Favorite - ) - ) - - @OptIn(ExperimentalDecomposeApi::class) - private val pagesNavigation = PagesNavigation() - - @OptIn(ExperimentalDecomposeApi::class) - override val pages: Value> = childPages( - source = pagesNavigation, - serializer = View.serializer(), - initialPages = { - Pages( - items = listOf( - View.Sponsor, - View.Home(shortcutIntent), - View.Favorite - ), - selectedIndex = when (shortcutIntent) { - is Shortcut.Intent.SEARCH -> 1 - is Shortcut.Intent.Series -> 1 - else -> 1 - } - ) - } - ) { config, context -> - createChild(config, context) - } - - @OptIn(ExperimentalDecomposeApi::class) - override val selectedPage: Value = pages.map { it.selectedIndex } - - override val sponsorScrollEnabled = MutableStateFlow(true) - override val homeScrollEnabled = MutableStateFlow(true) - override val favoriteScrollEnabled = MutableStateFlow(true) - - private val backCallback = BackCallback { - pageBack() - } - - init { - backHandler.register(backCallback) - } - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - InitialScreen(this) - } - } - - private fun createChild( - view: View, - componentContext: ComponentContext - ) : Component { - return when (view) { - is View.Sponsor -> SponsorScreenComponent( - componentContext = componentContext, - di = di - ) - is View.Home -> HomeScreenComponent( - componentContext = componentContext, - di = di, - shortcutIntent = view.shortcutIntent, - watchVideo = { schemeKey, series, episode, stream -> - watchVideo(schemeKey, series, episode, stream) - }, - scrollEnabled = { homeScrollEnabled.value = it } - ) - is View.Favorite -> FavoriteScreenComponent( - componentContext = componentContext, - di = di, - watchVideo = { schemeKey, series, episode, stream -> - watchVideo(schemeKey, series, episode, stream) - }, - scrollEnabled = { favoriteScrollEnabled.value = it } - ) - } - } - - @OptIn(ExperimentalDecomposeApi::class) - override fun selectPage(index: Int) { - pagesNavigation.select(index = index) { new, old -> - if (new.items[new.selectedIndex] == old.items[old.selectedIndex]) { - pageBack() - } - } - } - - @OptIn(ExperimentalDecomposeApi::class) - private fun pageBack() { - (pages.value.items[pages.value.selectedIndex].instance as? SeriesHolderComponent) - ?.dismissHoldingSeries() - ?: onBack() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/SeriesHolderComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/SeriesHolderComponent.kt deleted file mode 100644 index 98407ebb..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/SeriesHolderComponent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial - -import dev.datlag.burningseries.shared.ui.navigation.Component - -interface SeriesHolderComponent : Component { - fun dismissHoldingSeries() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/View.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/View.kt deleted file mode 100644 index 33f1e269..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/View.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial - -import dev.datlag.burningseries.model.Shortcut -import kotlinx.serialization.Serializable - -@Serializable -sealed class View { - - @Serializable - data object Sponsor : View() - - @Serializable - data class Home( - val shortcutIntent: Shortcut.Intent - ) : View() - - @Serializable - data object Favorite : View() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteComponent.kt deleted file mode 100644 index cab8f565..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteComponent.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.favorite - -import com.arkivanov.decompose.router.slot.ChildSlot -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.database.Series -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.screen.initial.SeriesHolderComponent -import kotlinx.coroutines.flow.StateFlow - -interface FavoriteComponent : SeriesHolderComponent { - - val favorites: StateFlow> - val searchItems: StateFlow> - - val child: Value> - - fun itemClicked(config: FavoriteConfig) - fun searchQuery(text: String) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteConfig.kt deleted file mode 100644 index d25d6bcb..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteConfig.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.favorite - -import dev.datlag.burningseries.database.common.mainTitle -import kotlinx.serialization.Serializable -import dev.datlag.burningseries.database.Series as DBSeries - -@Serializable -sealed class FavoriteConfig { - @Serializable - data class Series( - val title: String, - val href: String, - val coverHref: String? - ) : FavoriteConfig() { - constructor(item: DBSeries) : this(item.mainTitle, item.href, item.coverHref) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreen.kt deleted file mode 100644 index 0850391f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreen.kt +++ /dev/null @@ -1,121 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.favorite - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import dev.chrisbanes.haze.haze -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.LocalPadding -import dev.datlag.burningseries.shared.common.header -import dev.datlag.burningseries.shared.common.lifecycle.WindowSize -import dev.datlag.burningseries.shared.common.lifecycle.calculateWindowWidthSize -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.mergedLocalPadding -import dev.datlag.burningseries.shared.rememberIsTv -import dev.datlag.burningseries.shared.ui.custom.FloatingSearchButton -import dev.datlag.burningseries.shared.ui.custom.VerticalScrollbar -import dev.datlag.burningseries.shared.ui.custom.rememberScrollbarAdapter -import dev.datlag.burningseries.shared.ui.screen.initial.favorite.component.SeriesCard -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun FavoriteScreen(component: FavoriteComponent) { - when (calculateWindowWidthSize()) { - is WindowSize.Expanded -> { - if (rememberIsTv()) { - DefaultView(component) - } else { - ExpandedView(component) - } - } - else -> DefaultView(component) - } -} - -@Composable -private fun DefaultView(component: FavoriteComponent) { - val childState by component.child.subscribeAsState() - childState.child?.also { (_, instance) -> - instance.render() - } ?: MainView(component, Modifier.fillMaxWidth()) -} - -@Composable -private fun ExpandedView(component: FavoriteComponent) { - val childState by component.child.subscribeAsState() - - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - MainView(component, Modifier.weight(1F)) - - childState.child?.also { (_, instance) -> - Box( - modifier = Modifier.weight(2F) - ) { - instance.render() - } - } - } -} - -@Composable -private fun MainView(component: FavoriteComponent, modifier: Modifier = Modifier) { - Box( - modifier = modifier - ) { - Row( - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - val favorites by component.searchItems.collectAsStateWithLifecycle() - val listState = rememberLazyGridState() - - LazyVerticalGrid( - columns = GridCells.Adaptive(150.dp), - modifier = Modifier.weight(1F).haze(state = LocalHaze.current), - state = listState, - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = LocalPadding() - ) { - header { - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(SharedRes.strings.favorites), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - } - items(favorites, key = { it.hrefPrimary }) { fav -> - SeriesCard( - series = fav, - modifier = Modifier.width(150.dp).height(230.dp), - onClick = { - component.itemClicked(FavoriteConfig.Series(fav)) - } - ) - } - } - VerticalScrollbar(rememberScrollbarAdapter(listState)) - } - FloatingSearchButton( - modifier = Modifier.align(Alignment.BottomEnd).mergedLocalPadding(WindowInsets.ime.asPaddingValues(), 16.dp), - onTextChange = { - component.searchQuery(it) - } - ) - } -} diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreenComponent.kt deleted file mode 100644 index 70c5049d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/FavoriteScreenComponent.kt +++ /dev/null @@ -1,119 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.favorite - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.slot.* -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.database.BurningSeries -import dev.datlag.burningseries.database.Series -import dev.datlag.burningseries.model.algorithm.JaroWinkler -import dev.datlag.burningseries.model.common.safeSubList -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.ioScope -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.other.Crashlytics -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.screen.initial.series.SeriesScreenComponent -import dev.datlag.skeo.Stream -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.* -import org.kodein.di.DI -import org.kodein.di.instance -import dev.datlag.burningseries.model.Series as ModelSeries - -class FavoriteScreenComponent( - componentContext: ComponentContext, - override val di: DI, - private val watchVideo: (String, ModelSeries, ModelSeries.Episode, Collection) -> Unit, - private val scrollEnabled: (Boolean) -> Unit -) : FavoriteComponent, ComponentContext by componentContext { - - private val database: BurningSeries by di.instance() - override val favorites = database - .burningSeriesQueries - .favoriteSeries() - .asFlow() - .mapToList(ioDispatcher()) - .flowOn(ioDispatcher()) - .stateIn(ioScope(), SharingStarted.WhileSubscribed(), emptyList()) - - private val searchQuery: MutableStateFlow = MutableStateFlow("") - override val searchItems: StateFlow> = combine(favorites, searchQuery) { t1, t2 -> - if (t2.isBlank()) { - t1 - } else { - coroutineScope { - t1.map { - async { - when { - it.title.trim().equals(t2.trim(), true) -> it to 1.0 - it.title.trim().startsWith(t2.trim(), true) -> it to 0.95 - it.title.trim().contains(t2.trim(), true) -> it to 0.9 - else -> it to JaroWinkler.distance(it.title.trim(), t2.trim()) - } - } - }.awaitAll().filter { - it.second > 0.85 - }.sortedByDescending { it.second }.map { it.first }.safeSubList(0, 10) - } - } - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), emptyList()) - - private val navigation = SlotNavigation() - override val child: Value> = childSlot( - source = navigation, - serializer = FavoriteConfig.serializer(), - handleBackButton = false - ) { config, context -> - when (config) { - is FavoriteConfig.Series -> SeriesScreenComponent( - componentContext = context, - di = di, - initialTitle = config.title, - initialHref = config.href, - initialCoverHref = config.coverHref, - onGoBack = { - dismissHoldingSeries() - }, - watchVideo = { schemeKey, series, episode, stream -> - watchVideo(schemeKey, series, episode, stream) - } - ) - } - } - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - FavoriteScreen(this) - } - SideEffect { - Crashlytics.screen(this) - } - } - - override fun itemClicked(config: FavoriteConfig) { - navigation.activate(config) { - scrollEnabled(false) - } - } - - override fun searchQuery(text: String) { - ioScope().launchIO { - searchQuery.emit(text) - } - } - - override fun dismissHoldingSeries() { - navigation.dismiss(scrollEnabled) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/component/SeriesCard.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/component/SeriesCard.kt deleted file mode 100644 index d185a55f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/favorite/component/SeriesCard.kt +++ /dev/null @@ -1,94 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.favorite.component - -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.database.Series -import dev.datlag.burningseries.database.common.bestTitle -import dev.datlag.burningseries.database.common.mainTitle -import dev.datlag.burningseries.database.common.subTitle -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.shared.common.bottomShadowBrush -import dev.datlag.burningseries.shared.ui.custom.Cover -import dev.datlag.burningseries.shared.ui.theme.SchemeTheme -import dev.datlag.burningseries.shared.ui.theme.onPrimary -import dev.datlag.burningseries.shared.ui.theme.primary -import dev.datlag.burningseries.shared.ui.theme.rememberSchemeThemeDominantColorState - -@Composable -fun SeriesCard( - series: Series, - modifier: Modifier = Modifier, - onClick: (Series) -> Unit -) { - SchemeTheme( - key = series.hrefPrimary - ) { updater -> - Card( - modifier = modifier, - onClick = { - onClick(series) - } - ) { - Box( - modifier = Modifier.fillMaxSize() - ) { - val colorState = rememberSchemeThemeDominantColorState( - key = series.hrefPrimary, - applyMinContrast = true, - minContrastBackgroundColor = MaterialTheme.colorScheme.surfaceVariant - ) - - Cover( - key = series.coverHref, - data = series.coverHref?.let { BSUtil.getBurningSeriesLink(it) }, - contentDescription = series.bestTitle, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - onSuccess = { state -> - updater?.update(state.painter) - } - ) - - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .fillMaxWidth() - .bottomShadowBrush(colorState.primary) - .padding(16.dp) - .padding(top = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) - ) { - Text( - text = series.mainTitle, - style = MaterialTheme.typography.titleLarge, - maxLines = 2, - fontWeight = FontWeight.Bold, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(), - color = colorState.onPrimary - ) - series.subTitle?.let { - Text( - text = it, - modifier = Modifier.fillMaxWidth(), - color = colorState.onPrimary, - maxLines = 2 - ) - } - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/DialogConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/DialogConfig.kt deleted file mode 100644 index 835bfdea..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/DialogConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home - -import kotlinx.serialization.Serializable - -@Serializable -sealed class DialogConfig { - - @Serializable - data object Sekret : DialogConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeComponent.kt deleted file mode 100644 index abf1a797..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeComponent.kt +++ /dev/null @@ -1,32 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home - -import com.arkivanov.decompose.router.slot.ChildSlot -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.Release -import dev.datlag.burningseries.model.state.HomeState -import dev.datlag.burningseries.model.state.SearchState -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.SeriesHolderComponent -import kotlinx.coroutines.flow.StateFlow - -interface HomeComponent : SeriesHolderComponent { - - val child: Value> - val dialog: Value> - - val homeState: StateFlow - val release: StateFlow - - val searchState: StateFlow - val searchItems: StateFlow> - - val onDeviceReachable: StateFlow - - fun retryLoadingHome(): Any? - fun itemClicked(config: HomeConfig) - fun showDialog(config: DialogConfig) - fun searchQuery(text: String) - fun retryLoadingSearch() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeConfig.kt deleted file mode 100644 index 947c6544..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeConfig.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home - -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.Home -import kotlinx.serialization.Serializable - -@Serializable -sealed class HomeConfig { - - @Serializable - data class Series( - val title: String?, - val href: String, - val coverHref: String?, - ) : HomeConfig() { - constructor(series: Home.Series) : this(series.mainTitle, series.href, series.coverHref) - constructor(episode: Home.Episode) : this(episode.series ?: episode.fullTitle, episode.href, episode.coverHref) - constructor(item: Genre.Item) : this(item.mainTitle, item.href, null) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreen.kt deleted file mode 100644 index ca586bf1..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreen.kt +++ /dev/null @@ -1,133 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home - -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.model.state.HomeState -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.WindowSize -import dev.datlag.burningseries.shared.common.lifecycle.calculateWindowWidthSize -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.rememberIsTv -import dev.datlag.burningseries.shared.ui.custom.state.ErrorState -import dev.datlag.burningseries.shared.ui.custom.state.LoadingState -import dev.datlag.burningseries.shared.ui.screen.initial.home.component.HomeOverview -import dev.datlag.burningseries.shared.ui.screen.initial.home.component.SearchFAB -import dev.datlag.burningseries.shared.ui.screen.initial.home.component.SearchOverview -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun HomeScreen(component: HomeComponent) { - val homeState by component.homeState.collectAsStateWithLifecycle() - val dialogState by component.dialog.subscribeAsState() - - when (val currentState = homeState) { - is HomeState.Loading -> { - Box(Modifier.fillMaxHeight()) { - val searchItems by component.searchItems.collectAsStateWithLifecycle() - - if (searchItems.isEmpty()) { - LoadingState(SharedRes.strings.loading_home) - } else { - SearchOverview(searchItems, component, Modifier.fillMaxSize(), 16.dp) - } - - SearchFAB(component) - } - } - is HomeState.Error -> { - Box(Modifier.fillMaxHeight()) { - val reachable by component.onDeviceReachable.collectAsStateWithLifecycle() - val searchItems by component.searchItems.collectAsStateWithLifecycle() - - if (searchItems.isEmpty()) { - ErrorState( - text = SharedRes.strings.error_loading_home, - customText = { - if (!reachable) { - Text( - modifier = Modifier.fillMaxWidth(0.85F), - text = stringResource(SharedRes.strings.enable_custom_dns), - textAlign = TextAlign.Center - ) - } - } - ) { - component.retryLoadingHome() - } - } else { - SearchOverview(searchItems, component, Modifier.fillMaxSize(), 16.dp) - } - - SearchFAB(component) - } - } - is HomeState.Success -> { - when (calculateWindowWidthSize()) { - is WindowSize.Expanded -> { - if (rememberIsTv()) { - DefaultView(currentState.home, component) - } else { - ExpandedView(currentState.home, component) - } - } - else -> DefaultView(currentState.home, component) - } - } - } - - dialogState.child?.instance?.render() -} - -@Composable -private fun DefaultView(home: Home, component: HomeComponent) { - val childState by component.child.subscribeAsState() - childState.child?.instance?.render() ?: MainView(home, component, Modifier.fillMaxWidth()) -} - -@Composable -private fun ExpandedView(home: Home, component: HomeComponent) { - val childState by component.child.subscribeAsState() - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - MainView(home, component, Modifier.weight(1.5F)) - - childState.child?.also { (_, instance) -> - Box( - modifier = Modifier.weight(2F) - ) { - instance.render() - } - } - } -} - -@Composable -private fun MainView(home: Home, component: HomeComponent, modifier: Modifier = Modifier) { - Box(modifier = modifier.fillMaxHeight()) { - Row( - modifier = Modifier.padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - val searchItems by component.searchItems.collectAsStateWithLifecycle() - - if (searchItems.isEmpty()) { - HomeOverview(home, component) - } else { - SearchOverview(searchItems, component, Modifier.weight(1F)) - } - } - - SearchFAB(component) - } -} - diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreenComponent.kt deleted file mode 100644 index b2dbce72..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/HomeScreenComponent.kt +++ /dev/null @@ -1,198 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.slot.* -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.Release -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.Shortcut -import dev.datlag.burningseries.model.algorithm.JaroWinkler -import dev.datlag.burningseries.model.common.getDigitsOrNull -import dev.datlag.burningseries.model.common.safeCast -import dev.datlag.burningseries.model.common.safeSubList -import dev.datlag.burningseries.model.state.* -import dev.datlag.burningseries.network.state.HomeStateMachine -import dev.datlag.burningseries.network.state.ReleaseStateMachine -import dev.datlag.burningseries.network.state.SearchStateMachine -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.ioScope -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.other.Crashlytics -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.home.dialog.sekret.SekretDialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.SeriesScreenComponent -import dev.datlag.skeo.Stream -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.* -import org.kodein.di.DI -import org.kodein.di.instance -import org.kodein.di.instanceOrNull - -class HomeScreenComponent( - componentContext: ComponentContext, - override val di: DI, - private var shortcutIntent: Shortcut.Intent, - private val watchVideo: (String, Series, Series.Episode, Collection) -> Unit, - private val scrollEnabled: (Boolean) -> Unit -) : HomeComponent, ComponentContext by componentContext { - - private val homeStateMachine: HomeStateMachine by di.instance() - override val homeState: StateFlow = homeStateMachine.state.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), HomeState.Loading) - override val onDeviceReachable = homeState.map { - it.safeCast()?.onDeviceReachable ?: (it is HomeState.Loading) - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), true) - - private val searchStateMachine by di.instance() - override val searchState = searchStateMachine.state.flowOn( - context = ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = SearchState.Loading - ) - private val allSearchItems = searchState.mapNotNull { it.safeCast() }.map { it.genres.flatMap { g -> g.items } } - private val searchQuery: MutableStateFlow = MutableStateFlow("") - override val searchItems: StateFlow> = combine(allSearchItems, searchQuery) { t1, t2 -> - if (t2.isBlank()) { - emptyList() - } else { - coroutineScope { - t1.map { - async { - when { - it.title.trim().equals(t2.trim(), true) -> it to 1.0 - it.title.trim().startsWith(t2.trim(), true) -> it to 0.95 - it.title.trim().contains(t2.trim(), true) -> it to 0.9 - else -> it to JaroWinkler.distance(it.title.trim(), t2.trim()) - } - } - }.awaitAll().filter { - it.second > 0.85 - }.sortedByDescending { it.second }.map { it.first }.safeSubList(0, 10) - } - } - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), emptyList()) - - private val appVersion: String? by di.instanceOrNull("APP_VERSION") - private val releaseStateMachine: ReleaseStateMachine by di.instance() - override val release: StateFlow = releaseStateMachine.state.map { state -> - if (state is ReleaseState.Success) { - if (!appVersion.isNullOrBlank()) { - state.releases.filter { - (it.tagAsNumber?.toIntOrNull() ?: 0) > (appVersion?.getDigitsOrNull()?.toIntOrNull() ?: 0) - }.maxByOrNull { it.publishedAtSeconds } - } else { - state.releases.maxByOrNull { it.publishedAtSeconds } - } - } else { - null - } - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), null) - - private val navigation = SlotNavigation() - override val child: Value> = childSlot( - source = navigation, - serializer = HomeConfig.serializer(), - handleBackButton = false, - initialConfiguration = { - when (val current = shortcutIntent) { - is Shortcut.Intent.Series -> HomeConfig.Series(null, current.href, null) - else -> null - } - } - ) { config, context -> - when (config) { - is HomeConfig.Series -> SeriesScreenComponent( - componentContext = context, - di = di, - initialTitle = config.title, - initialHref = config.href, - initialCoverHref = config.coverHref, - onGoBack = { - dismissHoldingSeries() - }, - watchVideo = { schemeKey, series, episode, stream -> - watchVideo(schemeKey, series, episode, stream) - } - ) - } - } - - private val dialogNavigation = SlotNavigation() - override val dialog: Value> = childSlot( - key = "DialogChildSlot", - source = dialogNavigation, - serializer = DialogConfig.serializer() - ) { config, slotContext -> - when (config) { - DialogConfig.Sekret -> SekretDialogComponent( - componentContext = slotContext, - di = di, - onDismissed = dialogNavigation::dismiss - ) - } - } - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - HomeScreen(this) - } - SideEffect { - Crashlytics.screen(this) - } - } - - override fun retryLoadingHome(): Any? = ioScope().launchIO { - homeStateMachine.dispatch(HomeAction.Retry) - } - - override fun itemClicked(config: HomeConfig) { - navigation.activate(config) { - scrollEnabled(false) - } - } - - override fun showDialog(config: DialogConfig) { - dialogNavigation.activate(config) - } - - override fun dismissHoldingSeries() { - shortcutIntent = Shortcut.Intent.NONE - navigation.dismiss { success -> - scrollEnabled(success) - - if (!success) { - searchQuery.getAndUpdate { - if (it.isBlank()) { - it - } else { - "" - } - } - } - } - } - - override fun searchQuery(text: String) { - ioScope().launchIO { - searchQuery.emit(text) - } - } - - override fun retryLoadingSearch() { - ioScope().launchIO { - searchStateMachine.dispatch(SearchAction.Retry) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.kt deleted file mode 100644 index 00b55a6b..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.lazy.grid.LazyGridScope -import dev.datlag.burningseries.model.Release -import kotlinx.coroutines.flow.StateFlow - -expect fun LazyGridScope.DeviceContent(release: StateFlow, onDeviceReachable: StateFlow) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/EpisodeItem.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/EpisodeItem.kt deleted file mode 100644 index 3aacef4c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/EpisodeItem.kt +++ /dev/null @@ -1,95 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.LazyGridItemScope -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.shared.common.bounceClick -import dev.datlag.burningseries.shared.common.focusScale -import dev.datlag.burningseries.shared.ui.custom.AutoSizeText -import dev.datlag.burningseries.shared.ui.custom.CountryImage -import dev.datlag.burningseries.shared.ui.custom.Cover - -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) -@Composable -fun LazyGridItemScope.EpisodeItem(episode: Home.Episode, onclick: () -> Unit) { - ElevatedCard( - modifier = Modifier.animateItemPlacement().focusScale(1.02F).height(150.dp).bounceClick(), - onClick = onclick - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Surface( - shape = CardDefaults.elevatedShape - ) { - Cover( - key = episode.coverHref, - data = episode.coverHref?.let { BSUtil.getBurningSeriesLink(it) }, - contentDescription = episode.bestTitle, - modifier = Modifier - .aspectRatio(1F, true) - .clip(CardDefaults.elevatedShape) - .background(MaterialTheme.colorScheme.tertiaryContainer), - errorColorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onTertiaryContainer) - ) - } - Column( - modifier = Modifier.padding(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = episode.bestTitle, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.titleLarge, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - if (episode.series != null) { - Row( - modifier = Modifier.fillMaxWidth().weight(1F), - verticalAlignment = Alignment.CenterVertically - ) { - AutoSizeText( - text = episode.episode ?: episode.bestTitle, - maxLines = 2, - softWrap = true, - maxTextSize = LocalTextStyle.current.fontSize, - minTextSize = MaterialTheme.typography.labelSmall.fontSize - ) - } - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - val code = remember(episode.href) { episode.flags.firstOrNull()?.bestCountryCode } - if (code != null) { - CountryImage( - code = code, - description = episode.flags.firstOrNull()?.title, - iconSize = 24.dp - ) - } - Text( - text = episode.info - ) - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/HomeOverview.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/HomeOverview.kt deleted file mode 100644 index 058ee206..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/HomeOverview.kt +++ /dev/null @@ -1,109 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.chrisbanes.haze.haze -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.LocalPadding -import dev.datlag.burningseries.shared.common.header -import dev.datlag.burningseries.shared.other.StateSaver -import dev.datlag.burningseries.shared.ui.custom.VerticalScrollbar -import dev.datlag.burningseries.shared.ui.custom.rememberScrollbarAdapter -import dev.datlag.burningseries.shared.ui.screen.initial.home.DialogConfig -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeComponent -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeConfig -import dev.datlag.burningseries.shared.ui.theme.MaterialSymbols -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun RowScope.HomeOverview(home: Home, component: HomeComponent) { - val state = rememberLazyGridState( - initialFirstVisibleItemIndex = StateSaver.homeGridIndex, - initialFirstVisibleItemScrollOffset = StateSaver.homeGridOffset - ) - - LazyVerticalGrid( - columns = GridCells.Adaptive(400.dp), - modifier = Modifier.weight(1F).haze(state = LocalHaze.current), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = LocalPadding(), - state = state - ) { - DeviceContent(component.release, component.onDeviceReachable) - header { - Row( - modifier = Modifier.padding(top = 16.dp).fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - modifier = Modifier.weight(1F), - text = stringResource(SharedRes.strings.newest_episodes), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - if (!StateSaver.sekretLibraryLoaded) { - IconButton( - onClick = { - component.showDialog(DialogConfig.Sekret) - }, - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Icon( - imageVector = MaterialSymbols.rememberDeployedCodeAlert(), - contentDescription = stringResource(SharedRes.strings.sekret_unavailable_title) - ) - } - } - } - } - items(home.episodes, key = { - it.href - }) { episode -> - EpisodeItem(episode) { - component.itemClicked(HomeConfig.Series(episode)) - } - } - header { - Spacer(modifier = Modifier.size(48.dp)) - } - header { - Text( - text = stringResource(SharedRes.strings.newest_series), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - } - items(home.series, key = { - it.href - }) { series -> - SeriesItem(series) { - component.itemClicked(HomeConfig.Series(series)) - } - } - } - VerticalScrollbar(rememberScrollbarAdapter(state)) - - DisposableEffect(state) { - onDispose { - StateSaver.homeGridIndex = state.firstVisibleItemIndex - StateSaver.homeGridOffset = state.firstVisibleItemScrollOffset - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchFAB.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchFAB.kt deleted file mode 100644 index ed4780df..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchFAB.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.ime -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.SearchOff -import androidx.compose.material.icons.filled.YoutubeSearchedFor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.model.state.SearchState -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.mergedLocalPadding -import dev.datlag.burningseries.shared.ui.custom.FloatingSearchButton -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeComponent - -@Composable -fun BoxScope.SearchFAB(component: HomeComponent) { - val searchState by component.searchState.collectAsStateWithLifecycle() - - FloatingSearchButton( - modifier = Modifier.align(Alignment.BottomEnd).mergedLocalPadding(WindowInsets.ime.asPaddingValues(), 16.dp), - onTextChange = { - component.searchQuery(it) - }, - enabled = searchState !is SearchState.Loading, - icon = when (searchState) { - is SearchState.Loading -> Icons.Default.YoutubeSearchedFor - is SearchState.Success -> Icons.Default.Search - is SearchState.Error -> Icons.Default.SearchOff - }, - overrideOnClick = searchState !is SearchState.Success, - onClick = { - component.retryLoadingSearch() - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchOverview.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchOverview.kt deleted file mode 100644 index e0a1a8fc..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SearchOverview.kt +++ /dev/null @@ -1,64 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.chrisbanes.haze.haze -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.LocalPadding -import dev.datlag.burningseries.shared.common.onClick -import dev.datlag.burningseries.shared.ui.custom.VerticalScrollbar -import dev.datlag.burningseries.shared.ui.custom.rememberScrollbarAdapter -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeComponent -import dev.datlag.burningseries.shared.ui.screen.initial.home.HomeConfig -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun SearchOverview( - items: List, - component: HomeComponent, - modifier: Modifier = Modifier, - contentPadding: Dp = 0.dp -) { - val listState = rememberLazyListState() - - LazyColumn( - state = listState, - modifier = modifier.haze(state = LocalHaze.current), - verticalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = LocalPadding(contentPadding), - ) { - item { - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(SharedRes.strings.search), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - } - items(items) { item -> - Text( - modifier = Modifier.fillParentMaxWidth().clip(MaterialTheme.shapes.small).onClick { - component.itemClicked(HomeConfig.Series(item)) - }.padding(vertical = 16.dp), - text = item.bestTitle, - softWrap = true, - overflow = TextOverflow.Ellipsis - ) - } - } - VerticalScrollbar(rememberScrollbarAdapter(listState)) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SeriesItem.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SeriesItem.kt deleted file mode 100644 index d306e343..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/SeriesItem.kt +++ /dev/null @@ -1,94 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.lazy.grid.LazyGridItemScope -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.database.common.bestTitle -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.shared.common.bounceClick -import dev.datlag.burningseries.shared.common.focusScale -import dev.datlag.burningseries.shared.ui.custom.Cover -import dev.datlag.burningseries.database.Series as DBSeries - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LazyGridItemScope.SeriesItem(series: Home.Series, modifier: Modifier = Modifier, onClick: () -> Unit) { - SeriesItem( - title = series.bestTitle, - coverHref = series.coverHref, - modifier = modifier.animateItemPlacement(), - onClick = onClick - ) -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LazyGridItemScope.SeriesItem(series: DBSeries, modifier: Modifier = Modifier, onClick: () -> Unit) { - SeriesItem( - title = series.bestTitle, - coverHref = series.coverHref, - modifier = modifier.animateItemPlacement(), - onClick = onClick - ) -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LazyItemScope.SeriesItem(series: DBSeries, modifier: Modifier = Modifier, onClick: () -> Unit) { - SeriesItem( - title = series.bestTitle, - coverHref = series.coverHref, - modifier = modifier.animateItemPlacement(), - onClick = onClick - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun SeriesItem(title: String, coverHref: String?, modifier: Modifier = Modifier, onClick: () -> Unit) { - ElevatedCard( - modifier = modifier.focusScale(1.02F).height(150.dp).bounceClick(), - onClick = onClick - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Surface( - shape = CardDefaults.elevatedShape - ) { - Cover( - key = coverHref, - data = coverHref?.let { BSUtil.getBurningSeriesLink(it) }, - contentDescription = title, - modifier = Modifier - .aspectRatio(1F, true) - .clip(CardDefaults.elevatedShape) - .background(MaterialTheme.colorScheme.tertiaryContainer), - errorColorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onTertiaryContainer) - ) - } - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = title, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(bottom = 8.dp), - maxLines = 3 - ) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretComponent.kt deleted file mode 100644 index 7641ffda..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretComponent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.dialog.sekret - -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface SekretComponent : DialogComponent { -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialog.kt deleted file mode 100644 index c918b95c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialog.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.dialog.sekret - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun SekretDialog(component: SekretComponent) { - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.sekret_unavailable_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = stringResource(SharedRes.strings.sekret_unavailable_text)) - }, - confirmButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialogComponent.kt deleted file mode 100644 index ca75b122..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/dialog/sekret/SekretDialogComponent.kt +++ /dev/null @@ -1,27 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.dialog.sekret - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.shared.LocalDI -import org.kodein.di.DI - -class SekretDialogComponent( - componentContext: ComponentContext, - override val di: DI, - private val onDismissed: () -> Unit, -) : SekretComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - SekretDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/DialogConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/DialogConfig.kt deleted file mode 100644 index 520db0b7..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/DialogConfig.kt +++ /dev/null @@ -1,32 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import dev.datlag.burningseries.model.Series -import kotlinx.serialization.Serializable - -@Serializable -sealed class DialogConfig { - - @Serializable - data class Season( - val selected: Series.Season, - val seasons: List - ) : DialogConfig() - - @Serializable - data class Language( - val selected: Series.Language, - val languages: List - ) : DialogConfig() - - @Serializable - data class StreamUnavailable( - val series: Series, - val episode: Series.Episode - ) : DialogConfig() - - @Serializable - data class Activate( - val series: Series, - val episode: Series.Episode - ) : DialogConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesComponent.kt deleted file mode 100644 index 9a36e3a6..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesComponent.kt +++ /dev/null @@ -1,47 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import com.arkivanov.decompose.router.slot.ChildSlot -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.database.Episode -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.state.SeriesState -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow - -interface SeriesComponent : Component { - - val seriesState: StateFlow - - val child: Value> - val dialog: Value> - - val title: StateFlow - val subTitle: StateFlow - val href: StateFlow - val commonHref: StateFlow - val coverHref: StateFlow - val isFavorite: StateFlow - - val loadingEpisodeHref: StateFlow - - val dbEpisodes: StateFlow> - val nextEpisodeToWatch: Flow - - val nextSeasonToWatch: Flow - - val isAnime: StateFlow - - fun retryLoadingSeries(): Any? - - fun goBack() - - fun showDialog(config: DialogConfig) - - fun toggleFavorite(): Any? - fun itemClicked(episode: Series.Episode): Any? - fun itemLongClicked(episode: Series.Episode) - fun watchToggle(series: Series, episode: Series.Episode, watched: Boolean): Any? - fun switchToSeason(season: Series.Season): Any? -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesConfig.kt deleted file mode 100644 index 62d1e541..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesConfig.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import dev.datlag.burningseries.model.Series -import kotlinx.serialization.Serializable - -@Serializable -sealed class SeriesConfig { - - @Serializable - data class Activate( - val series: Series, - val episode: Series.Episode - ) : SeriesConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.kt deleted file mode 100644 index 39b74e38..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.kt +++ /dev/null @@ -1,338 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Redo -import androidx.compose.material.icons.filled.ArrowBackIosNew -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.FavoriteBorder -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import dev.chrisbanes.haze.haze -import dev.datlag.burningseries.database.Episode -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.state.SeriesState -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.LocalPadding -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.localPadding -import dev.datlag.burningseries.shared.other.StateSaver -import dev.datlag.burningseries.shared.ui.custom.Cover -import dev.datlag.burningseries.shared.ui.custom.VerticalScrollbar -import dev.datlag.burningseries.shared.ui.custom.rememberScrollbarAdapter -import dev.datlag.burningseries.shared.ui.custom.state.ErrorState -import dev.datlag.burningseries.shared.ui.custom.state.LoadingState -import dev.datlag.burningseries.shared.ui.screen.initial.series.component.DescriptionText -import dev.datlag.burningseries.shared.ui.screen.initial.series.component.EpisodeItem -import dev.datlag.burningseries.shared.ui.screen.initial.series.component.SeasonAndLanguageButtons -import dev.datlag.burningseries.shared.ui.screen.initial.series.component.AniFlowCard -import dev.datlag.burningseries.shared.ui.theme.SchemeTheme -import dev.datlag.burningseries.shared.ui.theme.TopLeftBottomRightRoundedShape -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun SeriesScreen(component: SeriesComponent) { - val href by component.commonHref.collectAsStateWithLifecycle() - val dialogState by component.dialog.subscribeAsState() - val childState by component.child.subscribeAsState() - - LaunchedEffect(href) { - SchemeTheme.setCommon(href) - } - - childState.child?.instance?.render() ?: run { - Box( - modifier = Modifier.fillMaxSize() - ) { - val loadingEpisode by component.loadingEpisodeHref.collectAsStateWithLifecycle() - val nextEpisode by component.nextEpisodeToWatch.collectAsStateWithLifecycle(initialValue = null) - val nextSeason by component.nextSeasonToWatch.collectAsStateWithLifecycle(initialValue = null) - val availableEpisode = if (nextEpisode?.hosters?.isNotEmpty() == true) { - nextEpisode - } else { - null - } - - DefaultScreen(component, loadingEpisode) - - availableEpisode?.let { next -> - ExtendedFloatingActionButton( - onClick = { - component.itemClicked(next) - }, - modifier = Modifier.align(Alignment.BottomEnd).localPadding(16.dp) - ) { - if (loadingEpisode.equals(next.href, ignoreCase = true)) { - CircularProgressIndicator( - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - } else { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = next.episodeTitle, - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - } - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text( - text = next.episodeTitle, - maxLines = 2 - ) - } - } - nextSeason?.let { next -> - ExtendedFloatingActionButton( - onClick = { - component.switchToSeason(next) - }, - modifier = Modifier.align(Alignment.BottomEnd).localPadding(16.dp) - ) { - val seasonText = if (next.title.toIntOrNull() != null) { - stringResource(SharedRes.strings.season_placeholder, next.title) - } else { - next.title - } - - Icon( - imageVector = Icons.AutoMirrored.Default.Redo, - contentDescription = next.title, - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = seasonText) - } - } - } - } - - DisposableEffect(Unit) { - onDispose { - SchemeTheme.setCommon(null) - } - } - - dialogState.child?.instance?.render() - EnterSeriesScreen() -} - -@Composable -expect fun EnterSeriesScreen() - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun DefaultScreen(component: SeriesComponent, loadingEpisode: String?) { - val seriesState by component.seriesState.collectAsStateWithLifecycle() - val title by component.title.collectAsStateWithLifecycle() - val coverHref by component.coverHref.collectAsStateWithLifecycle() - val commonHref by component.commonHref.collectAsStateWithLifecycle() - - when (val current = seriesState) { - is SeriesState.Loading -> { - LoadingState(SharedRes.strings.loading_series) - } - is SeriesState.Error -> { - ErrorState(SharedRes.strings.error_loading_series) { - component.retryLoadingSeries() - } - } - is SeriesState.Success -> { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - val state = rememberLazyListState( - initialFirstVisibleItemIndex = StateSaver.seriesListIndex, - initialFirstVisibleItemScrollOffset = StateSaver.seriesListOffset - ) - val dbEpisodes by component.dbEpisodes.collectAsStateWithLifecycle() - - LazyColumn( - state = state, - modifier = Modifier.weight(1F).haze(state = LocalHaze.current), - contentPadding = LocalPadding(16.dp) - ) { - item { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Box( - modifier = Modifier - .width(150.dp) - .height(200.dp) - .clip(MaterialTheme.shapes.medium) - .align(Alignment.CenterVertically) - ) { - val updater = SchemeTheme.create(commonHref) - - Cover( - modifier = Modifier.fillMaxSize(), - key = coverHref, - data = coverHref?.let { BSUtil.getBurningSeriesLink(it) }, - contentDescription = title.ifBlank { - stringResource(SharedRes.strings.loading_intent_series) - }, - onSuccess = { success -> - updater?.update(success.painter) - } - ) - IconButton( - modifier = Modifier.background(Color.Black.copy(alpha = 0.5F), shape = TopLeftBottomRightRoundedShape( - RoundedCornerShape(12.dp) - )), - onClick = { - component.goBack() - } - ) { - Icon( - imageVector = Icons.Default.ArrowBackIosNew, - contentDescription = stringResource(SharedRes.strings.back), - tint = Color.White - ) - } - } - Column( - modifier = Modifier.weight(1F), - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), - ) { - val subTitle by component.subTitle.collectAsStateWithLifecycle() - val isFavorite by component.isFavorite.collectAsStateWithLifecycle() - - IconButton( - modifier = Modifier.align(Alignment.End), - onClick = { - component.toggleFavorite() - } - ) { - Icon( - imageVector = if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, - contentDescription = stringResource(SharedRes.strings.favorite), - tint = if (isFavorite) Color.Red else LocalContentColor.current - ) - } - Spacer(Modifier.weight(1F)) - Text( - text = title.ifBlank { - stringResource(SharedRes.strings.loading_intent_series) - }, - maxLines = 2, - fontWeight = FontWeight.SemiBold, - overflow = TextOverflow.Ellipsis, - softWrap = true, - style = MaterialTheme.typography.titleLarge - ) - subTitle?.let { - Text( - text = it, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - } - Spacer(Modifier.weight(1F)) - } - } - } - - item { - DescriptionText(current.series.description) - - SeasonAndLanguageButtons( - selectedSeason = current.series.currentSeason, - selectedLanguage = current.series.currentLanguage, - seasons = current.series.seasons, - languages = current.series.languages, - onSeasonClick = { season -> - season?.let { - component.showDialog(DialogConfig.Season(it, current.series.seasons)) - } - }, - onLanguageClick = { language -> - language?.let { - component.showDialog(DialogConfig.Language(it, current.series.languages)) - } - } - ) - } - - item { - AniFlowCard( - isAnime = component.isAnime, - modifier = Modifier.fillParentMaxWidth().padding(vertical = 8.dp) - ) - } - - SeriesContent( - content = current.series, - dbEpisodes = dbEpisodes, - loadingEpisode = loadingEpisode, - onEpisodeClick = { - component.itemClicked(it) - }, - onEpisodeLongClick = { - component.itemLongClicked(it) - }, - onWatchToggle = { episode, watched -> - component.watchToggle(current.series, episode, watched) - } - ) - } - VerticalScrollbar(rememberScrollbarAdapter(state)) - - DisposableEffect(state) { - onDispose { - StateSaver.seriesListIndex = state.firstVisibleItemIndex - StateSaver.seriesListOffset = state.firstVisibleItemScrollOffset - } - } - } - } - } -} - -private fun LazyListScope.SeriesContent( - content: Series, - dbEpisodes: List, - loadingEpisode: String?, - onEpisodeClick: (Series.Episode) -> Unit, - onEpisodeLongClick: (Series.Episode) -> Unit, - onWatchToggle: (episode: Series.Episode, Boolean) -> Unit -) { - items(content.episodes, key = { it.href }) { episode -> - val dbEpisode = remember(dbEpisodes, episode.href) { - dbEpisodes.firstOrNull { it.href == episode.href } ?: dbEpisodes.firstOrNull { it.href.equals(episode.href, true) } - } - - EpisodeItem( - content = episode, - dbEpisode = dbEpisode, - isLoading = loadingEpisode.equals(episode.href, true), - onClick = { - onEpisodeClick(episode) - }, - onLongClick = { - onEpisodeLongClick(episode) - }, - onWatchToggle = { watched -> - onWatchToggle(episode, watched) - } - ) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreenComponent.kt deleted file mode 100644 index 5bc1f256..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreenComponent.kt +++ /dev/null @@ -1,434 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList -import app.cash.sqldelight.coroutines.mapToOneOrNull -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.slot.* -import com.arkivanov.decompose.value.Value -import com.arkivanov.essenty.backhandler.BackCallback -import dev.datlag.burningseries.database.BurningSeries -import dev.datlag.burningseries.database.common.convertedNumber -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.common.collectSafe -import dev.datlag.burningseries.model.common.safeCast -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.model.state.EpisodeAction -import dev.datlag.burningseries.model.state.EpisodeState -import dev.datlag.burningseries.model.state.SeriesAction -import dev.datlag.burningseries.model.state.SeriesState -import dev.datlag.burningseries.network.WrapAPI -import dev.datlag.burningseries.network.state.EpisodeStateMachine -import dev.datlag.burningseries.network.state.SeriesStateMachine -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.Sekret -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.ioScope -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.common.withMainContext -import dev.datlag.burningseries.shared.getPackageName -import dev.datlag.burningseries.shared.other.Crashlytics -import dev.datlag.burningseries.shared.other.StateSaver -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.ActivateScreenComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.activate.ActivateDialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.language.LanguageDialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.season.SeasonDialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.unavailable.UnavailableDialogComponent -import dev.datlag.skeo.Stream -import io.ktor.client.* -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.flow.* -import kotlinx.datetime.Clock -import kotlinx.serialization.json.Json -import org.kodein.di.DI -import org.kodein.di.instance -import kotlin.math.max - -class SeriesScreenComponent( - componentContext: ComponentContext, - override val di: DI, - private val initialTitle: String?, - private val initialHref: String, - private val initialCoverHref: String?, - private val onGoBack: () -> Unit, - private val watchVideo: (String, Series, Series.Episode, Collection) -> Unit -) : SeriesComponent, ComponentContext by componentContext { - - private val httpClient by di.instance() - private val json by di.instance() - private val wrapAPI by di.instance() - - private val seriesStateMachine = SeriesStateMachine( - client = httpClient, - href = initialHref, - json = json, - wrapAPI = wrapAPI, - wrapAPIKey = if (StateSaver.sekretLibraryLoaded) { - Sekret().wrapApi(getPackageName()) - } else { null }, - ) - override val seriesState: StateFlow = seriesStateMachine.state.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), SeriesState.Loading(initialHref)) - - private val successState = seriesState.mapNotNull { it.safeCast() }.flowOn(ioDispatcher()) - private val currentSeries = successState.map { it.series }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), null) - private val onDeviceReachable = successState.map { it.onDeviceReachable }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.Eagerly, true) - override val title: StateFlow = currentSeries.mapNotNull { it?.mainTitle }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), initialTitle ?: String()) - override val subTitle: StateFlow = currentSeries.mapNotNull { it?.subTitle }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), null) - override val href: StateFlow = currentSeries.mapNotNull { it?.href }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), BSUtil.fixSeriesHref(initialHref)) - override val commonHref: StateFlow = href.map { - val commonized = BSUtil.commonSeriesHref(it) - database.burningSeriesQueries.seriesUpdateHrefByCommonHref(it, commonized) - commonized - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), BSUtil.commonSeriesHref(initialHref)) - override val coverHref: StateFlow = currentSeries.mapNotNull { it?.coverHref }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), initialCoverHref) - - private val database: BurningSeries by di.instance() - override val isFavorite: StateFlow = commonHref.transform { - return@transform emitAll( - database - .burningSeriesQueries - .seriesByHref(it) - .asFlow() - .mapToOneOrNull(ioDispatcher()) - .map { s -> - s != null && s.favoriteSince > 0 - } - ) - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), database.burningSeriesQueries.seriesByHref(commonHref.value).executeAsOneOrNull()?.favoriteSince?.let { it > 0 } ?: false) - - private val episodeStateMachine by di.instance() - private val episodeState: StateFlow = episodeStateMachine.state.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), EpisodeState.Waiting) - override val loadingEpisodeHref: StateFlow = episodeState.map { - it.safeCast()?.episode?.href ?: it.safeCast()?.episode?.href - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), null) - - override val dbEpisodes = commonHref.transform { - return@transform emitAll(database.burningSeriesQueries.selectEpisodesBySeriesHref(it).asFlow().mapToList( - currentCoroutineContext() - )) - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), database.burningSeriesQueries.selectEpisodesBySeriesHref(commonHref.value).executeAsList()) - - override val nextEpisodeToWatch = combine(currentSeries.map { it?.episodes }, dbEpisodes) { seriesEpisodes, savedEpisodes -> - if (seriesEpisodes == null) { - null - } else { - val availableEpisodes = savedEpisodes.filter { - seriesEpisodes.any { s -> - s.href.equals(it.href, true) - } - } - - val maxWatchedEpisode = availableEpisodes.mapNotNull { - if (it.progress > 0L || it.progress == Long.MIN_VALUE) { - it - } else { - null - } - }.maxByOrNull { - it.convertedNumber ?: -1 - } - - if (maxWatchedEpisode == null) { - seriesEpisodes.firstOrNull() - } else { - val length = max(maxWatchedEpisode.length, 0L) - val progress = if (maxWatchedEpisode.progress == Long.MIN_VALUE) { - Long.MIN_VALUE - } else { - max(maxWatchedEpisode.progress, 0L) - } - - val isFinished = if (length > 0L && progress > 0L) { - (progress.toDouble() / length.toDouble() * 100.0).toFloat() >= 85F - } else { - progress == Long.MIN_VALUE - } - - val wantedNumber = if (isFinished) { - maxWatchedEpisode.convertedNumber?.plus(1)?.toString() - } else { - maxWatchedEpisode.convertedNumber?.toString() ?: maxWatchedEpisode.number - } - - if (!wantedNumber.isNullOrBlank()) { - seriesEpisodes.firstOrNull { - it.number.equals(wantedNumber, true) - } ?: seriesEpisodes.firstOrNull { - val compareNumber = wantedNumber.toIntOrNull() ?: return@firstOrNull false - it.convertedNumber == compareNumber - } - } else { - null - } - } - } - }.flowOn(ioDispatcher()) - - override val isAnime: StateFlow = currentSeries.filterNotNull().map { it.isAnime }.flowOn( - ioDispatcher() - ).stateIn( - scope = ioScope(), - started = SharingStarted.WhileSubscribed(), - initialValue = database.burningSeriesQueries.isSeriesAnime(commonHref.value).executeAsOneOrNull() ?: false - ) - - override val nextSeasonToWatch = combine( - currentSeries.map { it?.seasons }, - currentSeries.map { it?.currentSeason }, - nextEpisodeToWatch - ) { seasons, current, episode -> - if (episode != null) { - null - } else { - seasons?.firstOrNull { - it.value == current?.value?.plus(1) - } - } - }.flowOn(ioDispatcher()) - - private val navigation = SlotNavigation() - override val child: Value> = childSlot( - source = navigation, - serializer = SeriesConfig.serializer(), - handleBackButton = false - ) { config, context -> - when (config) { - is SeriesConfig.Activate -> ActivateScreenComponent( - componentContext = context, - di = di, - onDeviceReachable = onDeviceReachable.value, - episode = config.episode, - onGoBack = navigation::dismiss, - watchVideo = { watchVideo(commonHref.value, config.series, config.episode, listOf(it)) } - ) - } - } - - private val dialogNavigation = SlotNavigation() - private val _dialog = childSlot( - key = "DialogChildSlot", - source = dialogNavigation, - serializer = DialogConfig.serializer() - ) { config, slotContext -> - when (config) { - is DialogConfig.Season -> SeasonDialogComponent( - componentContext = slotContext, - di = di, - defaultSeason = config.selected, - seasons = config.seasons, - onDismissed = dialogNavigation::dismiss, - onSelected = { - loadNewSeason(it) - } - ) as DialogComponent - is DialogConfig.Language -> LanguageDialogComponent( - componentContext = slotContext, - di = di, - defaultLanguage = config.selected, - languages = config.languages, - onDismissed = dialogNavigation::dismiss, - onSelected = { - loadNewLanguage(it) - } - ) - is DialogConfig.StreamUnavailable -> UnavailableDialogComponent( - componentContext = slotContext, - di = di, - series = config.series, - episode = config.episode, - onDismissed = dialogNavigation::dismiss, - onActivate = { series, episode -> - navigation.activate(SeriesConfig.Activate(series, episode)) - } - ) - is DialogConfig.Activate -> ActivateDialogComponent( - componentContext = slotContext, - di = di, - series = config.series, - episode = config.episode, - onDismiss = dialogNavigation::dismiss, - onActivate = { series, episode -> - navigation.activate(SeriesConfig.Activate(series, episode)) - } - ) - } - } - override val dialog: Value> = _dialog - - private val backCallback = BackCallback { - onGoBack() - } - - init { - backHandler.register(backCallback) - - ioScope().launchIO { - episodeState.collectSafe { state -> - when (state) { - is EpisodeState.SuccessHoster -> { - saveSeriesAndEpisodeToDB() - } - is EpisodeState.SuccessStream -> { - val series = currentSeries.value ?: currentSeries.first() ?: currentSeries.value!! - - withMainContext { - watchVideo(commonHref.value, series, state.episode, state.results) - } - } - is EpisodeState.ErrorHoster, is EpisodeState.ErrorStream -> { - val episode = state.safeCast()?.episode - val series = currentSeries.value ?: currentSeries.first() ?: currentSeries.value!! - - if (episode != null) { - withMainContext { - showDialog( - DialogConfig.StreamUnavailable(series, episode) - ) - } - } - } - else -> { } - } - } - } - ioScope().launchIO { - currentSeries.filterNotNull().collectSafe { series -> - val isAnime = series.isAnime - val hrefPrimary = BSUtil.commonSeriesHref(series.href) - - if (isAnime) { - database.burningSeriesQueries.setSeriesAnime(hrefPrimary) - } else { - database.burningSeriesQueries.unsetSeriesAnime(hrefPrimary) - } - } - } - } - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - SeriesScreen(this) - } - SideEffect { - Crashlytics.screen(this) - } - } - - override fun retryLoadingSeries(): Any? = ioScope().launchIO { - seriesStateMachine.dispatch(SeriesAction.Retry) - } - - override fun goBack() { - onGoBack() - } - - override fun showDialog(config: DialogConfig) { - dialogNavigation.activate(config) - } - - override fun toggleFavorite() = ioScope().launchIO { - saveSeriesAndEpisodeToDB() - - database.burningSeriesQueries.updateSeriesFavoriteSince( - since = if (isFavorite.value) 0L else Clock.System.now().epochSeconds, - hrefPrimary = commonHref.value, - href = href.value, - title = title.value, - coverHref = coverHref.value, - anime = currentSeries.firstOrNull()?.isAnime ?: false - ) - } - - private suspend fun saveSeriesAndEpisodeToDB() { - database.burningSeriesQueries.insertSeriesOrIgnore( - hrefPrimary = commonHref.value, - href = href.value, - title = title.value, - coverHref = coverHref.value, - favoriteSince = if (isFavorite.value) 0L else Clock.System.now().epochSeconds, - anime = currentSeries.firstOrNull()?.isAnime ?: false - ) - - database.burningSeriesQueries.transaction { - currentSeries.value?.episodes?.forEach { episode -> - database.burningSeriesQueries.insertEpisode( - href = episode.href, - number = episode.number, - title = episode.title, - length = 0L, - progress = 0L, - seriesHref = commonHref.value - ) - - episode.hosters.forEach { hoster -> - database.burningSeriesQueries.insertHoster( - href = hoster.href, - title = hoster.title, - episodeHref = episode.href - ) - } - } - } - } - - override fun itemClicked(episode: Series.Episode): Any? = ioScope().launchIO { - episodeStateMachine.dispatch(EpisodeAction.Load(episode)) - } - - override fun itemLongClicked(episode: Series.Episode) { - currentSeries.value?.let { series -> - showDialog(DialogConfig.Activate( - series = series, - episode = episode - )) - } - } - - override fun watchToggle(series: Series, episode: Series.Episode, watched: Boolean): Any? = ioScope().launchIO { - val maxLength = suspendCatching { - database.burningSeriesQueries.selectEpisodeByHref(episode.href).executeAsOneOrNull()?.length - }.getOrNull() ?: 0L - - val progress = if (watched) { - 0L - } else { - if (maxLength == 0L) { - Long.MIN_VALUE - } else { - maxLength - } - } - - database.burningSeriesQueries.updateEpisodeProgress( - progress = progress, - href = episode.href, - number = episode.number, - title = episode.title, - length = maxLength, - seriesHref = BSUtil.commonSeriesHref(series.href) - ) - } - - override fun switchToSeason(season: Series.Season): Any? = loadNewSeason(season) - - private fun loadNewSeason(season: Series.Season) = ioScope().launchIO { - (currentSeries.value ?: currentSeries.firstOrNull())?.let { series -> - seriesStateMachine.dispatch(SeriesAction.Load(series.hrefBuilder(season.value))) - } - } - - private fun loadNewLanguage(language: Series.Language) = ioScope().launchIO { - (currentSeries.value ?: currentSeries.firstOrNull())?.let { series -> - seriesStateMachine.dispatch(SeriesAction.Load(series.hrefBuilder(language = language.value))) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateComponent.kt deleted file mode 100644 index 7909b21d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateComponent.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate - -import com.arkivanov.decompose.router.slot.ChildSlot -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component.DialogConfig -import kotlinx.coroutines.flow.StateFlow - -interface ActivateComponent : Component { - - val onDeviceReachable: Boolean - val episode: Series.Episode - val isSaving: StateFlow - - val dialog: Value> - - fun back() - fun onScraped(data: String) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreen.kt deleted file mode 100644 index 970e645d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreen.kt +++ /dev/null @@ -1,79 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate - -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBackIosNew -import androidx.compose.material.icons.filled.Save -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.localPadding -import dev.datlag.burningseries.shared.ui.custom.state.UnreachableState -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component.WebView -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun ActivateScreen(component: ActivateComponent) { - val dialogState by component.dialog.subscribeAsState() - val onDeviceReachable = remember { component.onDeviceReachable } - - Column( - modifier = Modifier.localPadding(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp), - verticalAlignment = Alignment.CenterVertically - ) { - val isSaving by component.isSaving.collectAsStateWithLifecycle() - - IconButton( - onClick = { - component.back() - } - ) { - Icon( - imageVector = Icons.Default.ArrowBackIosNew, - contentDescription = stringResource(SharedRes.strings.back) - ) - } - Text( - modifier = Modifier.weight(1F), - text = stringResource(SharedRes.strings.activate_hint), - maxLines = 2, - style = MaterialTheme.typography.titleMedium - ) - if (isSaving) { - Box( - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Save, - contentDescription = stringResource(SharedRes.strings.saving) - ) - CircularProgressIndicator() - } - } - } - if (onDeviceReachable) { - dialogState.child?.instance?.render() - WebView( - url = BSUtil.getBurningSeriesLink(component.episode.href), - modifier = Modifier.fillMaxSize(), - onScraped = { data -> - component.onScraped(data) - } - ) - } else { - UnreachableState(SharedRes.strings.activate_unreachable) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreenComponent.kt deleted file mode 100644 index 5c81392a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/ActivateScreenComponent.kt +++ /dev/null @@ -1,128 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.slot.SlotNavigation -import com.arkivanov.decompose.router.slot.activate -import com.arkivanov.decompose.router.slot.childSlot -import com.arkivanov.decompose.router.slot.dismiss -import com.arkivanov.essenty.backhandler.BackCallback -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.common.collectSafe -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.model.state.SaveAction -import dev.datlag.burningseries.model.state.SaveState -import dev.datlag.burningseries.network.state.SaveStateMachine -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.ioScope -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.common.withMainContext -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component.DialogConfig -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error.ErrorDialogComponent -import dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success.SuccessDialogComponent -import dev.datlag.skeo.Stream -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.serialization.json.Json -import org.kodein.di.DI -import org.kodein.di.instance - -class ActivateScreenComponent( - componentContext: ComponentContext, - override val di: DI, - override val onDeviceReachable: Boolean, - override val episode: Series.Episode, - private val onGoBack: () -> Unit, - private val watchVideo: (Stream) -> Unit -) : ActivateComponent, ComponentContext by componentContext { - - private val saveStateMachine by di.instance() - private val saveState = saveStateMachine.state.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), SaveState.Waiting) - private val json by di.instance() - private val savedData: MutableSet = mutableSetOf() - - override val isSaving = saveState.map { it is SaveState.Saving }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), saveState.value is SaveState.Saving) - - private val dialogNavigation = SlotNavigation() - override val dialog = childSlot( - source = dialogNavigation, - serializer = DialogConfig.serializer() - ) { config, slotContext -> - when (config) { - is DialogConfig.Success -> SuccessDialogComponent( - componentContext = slotContext, - di = di, - stream = config.stream, - onDismissed = dialogNavigation::dismiss, - watchVideo = { watchVideo(it) } - ) as DialogComponent - is DialogConfig.Error -> ErrorDialogComponent( - componentContext = slotContext, - di = di, - stream = config.stream, - onDismissed = dialogNavigation::dismiss, - watchVideo = { watchVideo(it) } - ) - } - } - - private val backCallback = BackCallback { - onGoBack() - } - - init { - backHandler.register(backCallback) - - ioScope().launchIO { - saveState.collectSafe { state -> - if (state is SaveState.Success) { - withMainContext { - dialogNavigation.activate(DialogConfig.Success(state.stream)) - } - } else if (state is SaveState.Error) { - withMainContext { - dialogNavigation.activate(DialogConfig.Error(state.stream)) - } - } - } - } - } - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - ActivateScreen(this) - } - } - - override fun back() { - onGoBack() - } - - override fun onScraped(data: String) { - val trimmed = data.trim() - if (trimmed.isNotBlank() && !trimmed.equals("null", true) && !trimmed.equals("undefined", true)) { - val converted = scopeCatching { - json.decodeFromString(trimmed) - }.getOrNull() - - if (converted != null) { - if (!savedData.contains(converted.href)) { - ioScope().launchIO { - saveStateMachine.dispatch(SaveAction.Save(converted)) - } - } - - savedData.add(converted.href) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/DialogConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/DialogConfig.kt deleted file mode 100644 index bb6a998f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/DialogConfig.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component - -import dev.datlag.skeo.Stream -import kotlinx.serialization.Serializable - -@Serializable -sealed class DialogConfig { - - @Serializable - data class Success(val stream: Stream?) : DialogConfig() - - @Serializable - data class Error(val stream: Stream?) : DialogConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.kt deleted file mode 100644 index dc7d5055..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -expect fun WebView(url: String, modifier: Modifier = Modifier, onScraped: (String) -> Unit) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorComponent.kt deleted file mode 100644 index 604d2e2a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error - -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.skeo.Stream - -interface ErrorComponent : DialogComponent { - val stream: Stream? - fun watch(stream: Stream) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.kt deleted file mode 100644 index 0d214bb9..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error - -import androidx.compose.runtime.Composable - -@Composable -expect fun ErrorDialog(component: ErrorComponent) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialogComponent.kt deleted file mode 100644 index be41edd3..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialogComponent.kt +++ /dev/null @@ -1,34 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.skeo.Stream -import org.kodein.di.DI - -class ErrorDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val stream: Stream?, - private val onDismissed: () -> Unit, - private val watchVideo: (Stream) -> Unit -) : ErrorComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - ErrorDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } - - override fun watch(stream: Stream) { - watchVideo(stream) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessComponent.kt deleted file mode 100644 index bbc9f40f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success - -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.skeo.Stream - -interface SuccessComponent : DialogComponent { - val stream: Stream? - fun watch(stream: Stream) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.kt deleted file mode 100644 index e59381f8..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success - -import androidx.compose.runtime.Composable - -@Composable -expect fun SuccessDialog(component: SuccessComponent) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialogComponent.kt deleted file mode 100644 index 0204c253..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialogComponent.kt +++ /dev/null @@ -1,35 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.skeo.Stream -import org.kodein.di.DI - -class SuccessDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val stream: Stream?, - private val onDismissed: () -> Unit, - private val watchVideo: (Stream) -> Unit -) : SuccessComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - SuccessDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } - - override fun watch(stream: Stream) { - watchVideo(stream) - dismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.kt deleted file mode 100644 index 199e3fbf..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import kotlinx.coroutines.flow.StateFlow - -/** - * Keep single implementation instead of ProjectCard, to support hint for not connected. - */ -@Composable -expect fun AniFlowCard( - isAnime: StateFlow, - modifier: Modifier = Modifier -) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt deleted file mode 100644 index d0327db3..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt +++ /dev/null @@ -1,51 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ExpandLess -import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.onClick -import dev.datlag.burningseries.shared.ui.custom.ExpandableText -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun DescriptionText(description: String) { - var expanded by remember { mutableStateOf(false) } - - ExpandableText( - expanded = expanded, - text = description, - collapsedMaxLines = 2, - modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp).animateContentSize().onClick { - expanded = !expanded - }, - toggle = { - IconButton( - onClick = { - expanded = !expanded - } - ) { - val (icon, iconDescription) = if (expanded) { - Icons.Default.ExpandLess to stringResource(SharedRes.strings.read_less) - } else { - Icons.Default.ExpandMore to stringResource(SharedRes.strings.read_more) - } - - Icon( - imageVector = icon, - contentDescription = iconDescription, - tint = MaterialTheme.colorScheme.primary - ) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/EpisodeItem.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/EpisodeItem.kt deleted file mode 100644 index 491209bd..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/EpisodeItem.kt +++ /dev/null @@ -1,151 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.PlayCircleFilled -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.vanniktech.blurhash.BlurHash -import dev.datlag.burningseries.database.Episode -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.* -import dev.datlag.burningseries.shared.ui.theme.TopLeftBottomRightRoundedShape -import dev.icerock.moko.resources.compose.stringResource -import kotlin.math.max -import kotlin.math.roundToInt - -@Composable -fun EpisodeItem( - content: Series.Episode, - dbEpisode: Episode?, - isLoading: Boolean, - onClick: () -> Unit, - onLongClick: () -> Unit, - onWatchToggle: (Boolean) -> Unit -) { - val blurHash = remember(content.href) { BlurHash.random() } - val enabled = content.hosters.isNotEmpty() - - val length = remember(dbEpisode?.length) { - max(dbEpisode?.length ?: 0L, 0L) - } - val progress = remember(dbEpisode?.progress) { - val value = dbEpisode?.progress ?: 0L - - if (value == Long.MIN_VALUE) { - Long.MIN_VALUE - } else { - max(value, 0L) - } - } - val isFinished = remember(length, progress) { - if (length > 0L && progress > 0L) { - (progress.toDouble() / length.toDouble() * 100.0).toFloat() >= 85F - } else { - progress == Long.MIN_VALUE - } - } - - Row( - modifier = Modifier - .padding(vertical = 4.dp) - .fillMaxWidth() - .focusScale(1.02F) - .height(100.dp) - .clip(MaterialTheme.shapes.medium) - .onClick( - enabled = enabled, - onDoubleClick = { - onWatchToggle(isFinished) - }, - onLongClick = onLongClick, - onClick = onClick - ).ifTrue(enabled) { bounceClick(0.95F) }.ifFalse(enabled) { alpha(0.5F) }, - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .fillMaxHeight() - .aspectRatio(1.75F, true) - .clip(MaterialTheme.shapes.medium), - contentAlignment = Alignment.Center - ) { - val imageBitmap by remember(blurHash) { - derivedStateOf { - BlurHash.decode(hash = blurHash, width = 175.dp.value.roundToInt(), height = 100.dp.value.roundToInt()) - } - } - imageBitmap?.let { - Image( - modifier = Modifier.fillMaxSize(), - bitmap = it, - contentDescription = content.title - ) - } ?: Box(Modifier.fillMaxSize().background(MaterialTheme.colorScheme.primaryContainer)) - Box(modifier = Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.5F))) - if (isFinished) { - Icon( - imageVector = Icons.Default.CheckCircle, - contentDescription = content.title, - tint = MaterialTheme.colorScheme.primary - ) - } else { - Icon( - imageVector = Icons.Default.PlayCircleFilled, - contentDescription = content.title, - tint = Color.White - ) - } - Text( - modifier = Modifier.clip(TopLeftBottomRightRoundedShape( - baseShape = MaterialTheme.shapes.medium, - otherSideRounding = 0.dp - )).background(Color.White).align(Alignment.BottomEnd).padding(4.dp), - text = content.episodeNumber, - color = Color.Black, - fontWeight = FontWeight.SemiBold, - maxLines = 1 - ) - if (isLoading) { - CircularProgressIndicator() - } - } - Column( - modifier = Modifier.fillMaxHeight().weight(1F) - ) { - Box( - modifier = Modifier.fillMaxWidth().weight(1F), - contentAlignment = Alignment.CenterStart - ) { - Text( - text = content.episodeTitle, - maxLines = 3 - ) - } - if (length != 0L && max(progress, 0L) != 0L) { - Text( - text = stringResource(SharedRes.strings.episode_progress, max(progress, 0L).toDuration(), length.toDuration()), - style = MaterialTheme.typography.labelSmall - ) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/SeasonAndLanguageButtons.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/SeasonAndLanguageButtons.kt deleted file mode 100644 index 4f750791..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/SeasonAndLanguageButtons.kt +++ /dev/null @@ -1,66 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.ui.custom.CountryImage -import dev.icerock.moko.resources.compose.stringResource - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun SeasonAndLanguageButtons( - selectedSeason: Series.Season?, - selectedLanguage: Series.Language?, - seasons: List, - languages: List, - onSeasonClick: (Series.Season?) -> Unit, - onLanguageClick: (Series.Language?) -> Unit, - modifier: Modifier = Modifier -) { - FlowRow( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (selectedSeason != null) { - Button( - onClick = { - onSeasonClick(selectedSeason) - }, - enabled = seasons.size > 1, - modifier = Modifier.weight(1F) - ) { - val seasonText = if (selectedSeason.title.toIntOrNull() != null) { - stringResource(SharedRes.strings.season_placeholder, selectedSeason.title) - } else { - selectedSeason.title - } - Text(text = seasonText) - } - } - if (selectedLanguage != null) { - Button( - onClick = { - onLanguageClick(selectedLanguage) - }, - enabled = languages.size > 1, - modifier = Modifier.weight(1F) - ) { - CountryImage( - code = selectedLanguage.value, - description = selectedLanguage.title, - iconSize = ButtonDefaults.IconSize - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = selectedLanguage.title) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateComponent.kt deleted file mode 100644 index 84e19d65..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateComponent.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.activate - -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface ActivateComponent : DialogComponent { - val series: Series - val episode: Series.Episode - - fun activate() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialog.kt deleted file mode 100644 index 9e7c09af..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialog.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.activate - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun ActivateDialog(component: ActivateComponent) { - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.activate_episode_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = stringResource(SharedRes.strings.activate_episode_text)) - }, - confirmButton = { - Button( - onClick = { - component.activate() - } - ) { - Text(text = stringResource(SharedRes.strings.activate)) - } - }, - dismissButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.later)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageComponent.kt deleted file mode 100644 index 36c5473b..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.language - -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface LanguageComponent : DialogComponent { - - val defaultLanguage: Series.Language - val languages: List - - fun onConfirm(language: Series.Language) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialog.kt deleted file mode 100644 index 070f949c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialog.kt +++ /dev/null @@ -1,111 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.language - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.ui.custom.CountryImage -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun LanguageDialog(component: LanguageComponent) { - var selectedItem by remember { mutableStateOf(component.defaultLanguage) } - - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.select_language), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Column( - modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - component.languages.forEach { - val selected = selectedItem == it - Row( - modifier = Modifier.selectable( - selected = selected, - role = Role.RadioButton, - onClick = { selectedItem = it } - ).fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selected, - onClick = null - ) - CountryImage( - code = it.value, - description = it.title, - iconSize = 24.dp - ) - Text( - text = it.title, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - } - } - } - }, - confirmButton = { - Button( - onClick = { - if (component.defaultLanguage != selectedItem) { - component.onConfirm(selectedItem) - } else { - component.dismiss() - } - } - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(SharedRes.strings.confirm), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.confirm)) - } - }, - dismissButton = { - Button( - onClick = { - component.dismiss() - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Icon( - imageVector = Icons.Default.Clear, - contentDescription = stringResource(SharedRes.strings.close), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.close)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialogComponent.kt deleted file mode 100644 index 3ce8f063..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/language/LanguageDialogComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.language - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.LocalDI -import org.kodein.di.DI - -class LanguageDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val defaultLanguage: Series.Language, - override val languages: List, - private val onDismissed: () -> Unit, - private val onSelected: (Series.Language) -> Unit -) : LanguageComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - LanguageDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } - - override fun onConfirm(language: Series.Language) { - onSelected(language) - dismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonComponent.kt deleted file mode 100644 index ae19d5f3..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.season - -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface SeasonComponent : DialogComponent { - - val defaultSeason: Series.Season - val seasons: List - - fun onConfirm(season: Series.Season) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialog.kt deleted file mode 100644 index abf917cb..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialog.kt +++ /dev/null @@ -1,110 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.season - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun SeasonDialog(component: SeasonComponent) { - var selectedItem by remember { mutableStateOf(component.defaultSeason) } - - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.select_season), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Column( - modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - component.seasons.forEach { - val selected = selectedItem == it - Row( - modifier = Modifier.selectable( - selected = selected, - role = Role.RadioButton, - onClick = { selectedItem = it } - ).fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selected, - onClick = null - ) - val seasonText = if (it.title.toIntOrNull() != null) { - stringResource(SharedRes.strings.season_placeholder, it.title) - } else { - it.title - } - Text( - text = seasonText, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - } - } - } - }, - confirmButton = { - Button( - onClick = { - if (component.defaultSeason != selectedItem) { - component.onConfirm(selectedItem) - } else { - component.dismiss() - } - } - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(SharedRes.strings.confirm), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.confirm)) - } - }, - dismissButton = { - Button( - onClick = { - component.dismiss() - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Icon( - imageVector = Icons.Default.Clear, - contentDescription = stringResource(SharedRes.strings.close), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.close)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialogComponent.kt deleted file mode 100644 index 184fbcf8..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/season/SeasonDialogComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.season - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.LocalDI -import org.kodein.di.DI - -class SeasonDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val defaultSeason: Series.Season, - override val seasons: List, - private val onDismissed: () -> Unit, - private val onSelected: (Series.Season) -> Unit -) : SeasonComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - SeasonDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } - - override fun onConfirm(season: Series.Season) { - onSelected(season) - dismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableComponent.kt deleted file mode 100644 index af3b7760..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.unavailable - -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface UnavailableComponent : DialogComponent { - - val series: Series - val episode: Series.Episode - - fun activate() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialog.kt deleted file mode 100644 index 0bf89c03..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialog.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.unavailable - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.style.TextOverflow -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun UnavailableDialog(component: UnavailableComponent) { - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.stream_unavailable_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Text(text = stringResource(SharedRes.strings.stream_unavailable_text)) - }, - confirmButton = { - Button( - onClick = { - component.activate() - } - ) { - Text(text = stringResource(SharedRes.strings.activate)) - } - }, - dismissButton = { - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.later)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialogComponent.kt deleted file mode 100644 index 730b307b..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/unavailable/UnavailableDialogComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.unavailable - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.LocalDI -import org.kodein.di.DI - -class UnavailableDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val series: Series, - override val episode: Series.Episode, - private val onDismissed: () -> Unit, - private val onActivate: (Series, Series.Episode) -> Unit -) : UnavailableComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - UnavailableDialog(this) - } - } - - override fun dismiss() { - onDismissed() - } - - override fun activate() { - onActivate(series, episode) - dismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorComponent.kt deleted file mode 100644 index a180f4a9..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorComponent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.sponsor - -import dev.datlag.burningseries.shared.ui.navigation.Component - -interface SponsorComponent : Component { -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreen.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreen.kt deleted file mode 100644 index 45aecd8d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreen.kt +++ /dev/null @@ -1,138 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.sponsor - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.chrisbanes.haze.haze -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.LocalHaze -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.* -import dev.datlag.burningseries.shared.other.Constants -import dev.datlag.burningseries.shared.other.Project -import dev.datlag.burningseries.shared.ui.custom.ProjectCard -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun SponsorScreen(component: SponsorComponent) { - LazyColumn( - modifier = Modifier.fillMaxSize().haze(state = LocalHaze.current), - contentPadding = LocalPadding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - item { - Text( - text = stringResource(SharedRes.strings.sponsor), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - } - item { - Text( - text = stringResource(SharedRes.strings.sponsor_text) - ) - } - item { - Text( - text = stringResource(SharedRes.strings.amount_text) - ) - } - item { - FlowRow( - modifier = Modifier.fillParentMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - val di = LocalDI.current - - Button( - onClick = { - Constants.Sponsor.GITHUB.openInBrowser(di) - }, - colors = ButtonDefaults.githubColors() - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.GitHub), - contentDescription = stringResource(SharedRes.strings.github), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.github)) - } - Button( - onClick = { - Constants.Sponsor.POLAR.openInBrowser(di) - }, - colors = ButtonDefaults.polarColors() - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.Polar), - contentDescription = stringResource(SharedRes.strings.polar), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.polar)) - } - Button( - onClick = { - Constants.Sponsor.PATREON.openInBrowser(di) - }, - colors = ButtonDefaults.patreonColors() - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.Patreon), - contentDescription = stringResource(SharedRes.strings.patreon), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.patreon)) - } - Button( - onClick = { - Constants.Sponsor.PAYPAL.openInBrowser(di) - }, - colors = ButtonDefaults.paypalColors() - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.PayPal), - contentDescription = stringResource(SharedRes.strings.paypal), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.paypal)) - } - } - } - item { - Text( - modifier = Modifier.padding(top = 32.dp), - text = stringResource(SharedRes.strings.free_support), - style = MaterialTheme.typography.headlineLarge, - fontWeight = FontWeight.Bold - ) - } - item { - Text( - text = stringResource(SharedRes.strings.free_support_text) - ) - } - item { - ProjectCard(Project.PulZ, modifier = Modifier.fillParentMaxWidth()) - } - item { - ProjectCard(Project.AniFlow, modifier = Modifier.fillParentMaxWidth()) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreenComponent.kt deleted file mode 100644 index 4016a76a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/sponsor/SponsorScreenComponent.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.sponsor - - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.shared.LocalDI -import dev.datlag.burningseries.shared.other.Crashlytics -import org.kodein.di.DI - -class SponsorScreenComponent( - componentContext: ComponentContext, - override val di: DI -) : SponsorComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { - SponsorScreen(this) - } - SideEffect { - Crashlytics.screen(this) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/DialogConfig.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/DialogConfig.kt deleted file mode 100644 index c755e3ee..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/DialogConfig.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import kotlinx.serialization.Serializable - -@Serializable -sealed class DialogConfig { - - @Serializable - data class Subtitle( - val list: List - ) : DialogConfig() - - @Serializable - data object Cast : DialogConfig() -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoComponent.kt deleted file mode 100644 index b24a82e5..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import com.arkivanov.decompose.router.slot.ChildSlot -import com.arkivanov.decompose.value.Value -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.ui.navigation.Component -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.skeo.Stream -import kotlinx.coroutines.flow.StateFlow -import kotlinx.serialization.Serializable - -interface VideoComponent : Component { - - val series: Series - val episode: StateFlow - val streams: List - - val selectedSubtitle: StateFlow - val startingPos: StateFlow - - val dialog: Value> - - fun back() - fun ended() - fun lengthUpdate(millis: Long) - fun progressUpdate(millis: Long) - - fun selectSubtitle(subtitles: List) - fun selectCast() - - @Serializable - data class Subtitle( - val code: String, - val title: String - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreenComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreenComponent.kt deleted file mode 100644 index a106f3e0..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreenComponent.kt +++ /dev/null @@ -1,160 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToOneOrNull -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.slot.* -import com.arkivanov.decompose.value.Value -import com.arkivanov.essenty.backhandler.BackCallback -import dev.datlag.burningseries.database.BurningSeries -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.common.collectSafe -import dev.datlag.burningseries.model.common.safeCast -import dev.datlag.burningseries.model.state.EpisodeState -import dev.datlag.burningseries.network.state.EpisodeStateMachine -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.ioScope -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.other.Crashlytics -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.video.dialog.cast.CastDialogComponent -import dev.datlag.burningseries.shared.ui.screen.video.dialog.subtitle.SubtitleDialogComponent -import dev.datlag.burningseries.shared.ui.theme.SchemeTheme -import dev.datlag.skeo.Stream -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import org.kodein.di.DI -import org.kodein.di.instance -import kotlin.math.max - -class VideoScreenComponent( - componentContext: ComponentContext, - override val di: DI, - private val schemeKey: String, - override val series: Series, - private val initialEpisode: Series.Episode, - private val initialStreams: List, - private val onBack: () -> Unit -) : VideoComponent, ComponentContext by componentContext { - - override val streams: List = initialStreams.sortedBy { it.headers.size } - private val episodeStateMachine by di.instance() - override val episode: StateFlow = episodeStateMachine.state.mapNotNull { it.safeCast() }.map { it.episode }.flowOn( - ioDispatcher() - ).stateIn(ioScope(), SharingStarted.WhileSubscribed(), initialEpisode) - - private val database by di.instance() - private val dbEpisode = episode.transform { - return@transform emitAll(database.burningSeriesQueries.selectEpisodeByHref(it.href).asFlow().mapToOneOrNull( - currentCoroutineContext() - )) - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), database.burningSeriesQueries.selectEpisodeByHref(episode.value.href).executeAsOneOrNull()) - - override val selectedSubtitle = MutableStateFlow(null) - - override val startingPos: StateFlow = dbEpisode.transform { - return@transform emit(max(it?.progress ?: 0L, 0L)) - }.flowOn(ioDispatcher()).stateIn(ioScope(), SharingStarted.WhileSubscribed(), dbEpisode.value?.progress ?: 0L) - - private val dialogNavigation = SlotNavigation() - override val dialog: Value> = childSlot( - source = dialogNavigation, - serializer = DialogConfig.serializer() - ) { config, slotContext -> - when (config) { - is DialogConfig.Subtitle -> SubtitleDialogComponent( - componentContext = slotContext, - di = di, - initialChosen = selectedSubtitle.value, - list = config.list, - onDismiss = dialogNavigation::dismiss, - onChosen = { - selectedSubtitle.value = it - } - ) as DialogComponent - is DialogConfig.Cast -> CastDialogComponent( - componentContext = slotContext, - di = di, - onDismiss = dialogNavigation::dismiss - ) - } - } - - private val backPressCounter = MutableStateFlow(0) - private val backCallback = BackCallback { - if (backPressCounter.value >= 1) { - back() - } else { - backPressCounter.update { it + 1 } - } - } - - init { - backHandler.register(backCallback) - - ioScope().launchIO { - backPressCounter.collectSafe { count -> - if (count > 0) { - delay(2000) - backPressCounter.emit(0) - } - } - } - } - - @Composable - override fun render() { - SchemeTheme(schemeKey) { - VideoScreen(this) - } - SideEffect { - Crashlytics.screen(this) - } - } - - override fun back() { - onBack() - } - - override fun ended() { - // ToDo("load next episode") - } - - override fun lengthUpdate(millis: Long) { - val currentEpisode = episode.value - - database.burningSeriesQueries.updateEpisodeLength( - length = max(millis, 0L), - href = currentEpisode.href, - number = currentEpisode.number, - title = currentEpisode.title, - progress = 0L, - seriesHref = BSUtil.commonSeriesHref(series.href) - ) - } - - override fun progressUpdate(millis: Long) { - val currentEpisode = episode.value - - database.burningSeriesQueries.updateEpisodeProgress( - progress = millis, - href = currentEpisode.href, - number = currentEpisode.number, - title = currentEpisode.title, - length = 0L, - seriesHref = BSUtil.commonSeriesHref(series.href) - ) - } - - override fun selectSubtitle(subtitles: List) { - dialogNavigation.activate(DialogConfig.Subtitle(subtitles)) - } - - override fun selectCast() { - dialogNavigation.activate(DialogConfig.Cast) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastComponent.kt deleted file mode 100644 index 3629810e..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastComponent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.cast - -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent - -interface CastComponent : DialogComponent { -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.kt deleted file mode 100644 index b27d0b1b..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.cast - -import androidx.compose.runtime.Composable - -@Composable -expect fun CastDialog(component: CastComponent) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialogComponent.kt deleted file mode 100644 index 2558acba..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialogComponent.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.cast - -import androidx.compose.runtime.Composable -import com.arkivanov.decompose.ComponentContext -import org.kodein.di.DI - -class CastDialogComponent( - componentContext: ComponentContext, - override val di: DI, - private val onDismiss: () -> Unit, -) : CastComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - CastDialog(this) - } - - override fun dismiss() { - onDismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleComponent.kt deleted file mode 100644 index ef4d505d..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.subtitle - -import dev.datlag.burningseries.shared.ui.navigation.DialogComponent -import dev.datlag.burningseries.shared.ui.screen.video.VideoComponent - -interface SubtitleComponent : DialogComponent { - - val initialChosen: VideoComponent.Subtitle? - val list: List - - fun choose(target: VideoComponent.Subtitle?) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialog.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialog.kt deleted file mode 100644 index 50489db4..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialog.kt +++ /dev/null @@ -1,137 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.subtitle - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.selection.selectable -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.SubtitlesOff -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.ui.custom.CountryImage -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun SubtitleDialog(component: SubtitleComponent) { - var selectedItem by remember { mutableStateOf(component.initialChosen) } - - AlertDialog( - onDismissRequest = { - component.dismiss() - }, - title = { - Text( - text = stringResource(SharedRes.strings.select_subtitle), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - }, - text = { - Column( - modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Row( - modifier = Modifier.selectable( - selected = selectedItem == null, - role = Role.RadioButton, - onClick = { selectedItem = null } - ).fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selectedItem == null, - onClick = null - ) - Icon( - imageVector = Icons.Default.SubtitlesOff, - contentDescription = stringResource(SharedRes.strings.subtitles_off), - modifier = Modifier.size(24.dp) - ) - Text( - text = stringResource(SharedRes.strings.subtitles_off), - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - } - component.list.forEach { - val selected = selectedItem == it - Row( - modifier = Modifier.selectable( - selected = selected, - role = Role.RadioButton, - onClick = { selectedItem = it } - ).fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selected, - onClick = null - ) - CountryImage( - code = it.code, - description = it.title, - iconSize = 24.dp - ) - Text( - text = it.title, - overflow = TextOverflow.Ellipsis, - softWrap = true - ) - } - } - } - }, - confirmButton = { - Button( - onClick = { - if (component.initialChosen != selectedItem) { - component.choose(selectedItem) - } else { - component.dismiss() - } - } - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(SharedRes.strings.confirm), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.confirm)) - } - }, - dismissButton = { - Button( - onClick = { - component.dismiss() - }, - modifier = Modifier.padding(bottom = 8.dp), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer - ) - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = stringResource(SharedRes.strings.close), - modifier = Modifier.size(ButtonDefaults.IconSize) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.close)) - } - } - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialogComponent.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialogComponent.kt deleted file mode 100644 index e5429a2f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/subtitle/SubtitleDialogComponent.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.subtitle - -import androidx.compose.runtime.Composable -import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.shared.ui.screen.video.VideoComponent -import org.kodein.di.DI - -class SubtitleDialogComponent( - componentContext: ComponentContext, - override val di: DI, - override val initialChosen: VideoComponent.Subtitle?, - override val list: List, - private val onDismiss: () -> Unit, - private val onChosen: (VideoComponent.Subtitle?) -> Unit -) : SubtitleComponent, ComponentContext by componentContext { - - @Composable - override fun render() { - SubtitleDialog(this) - } - - override fun dismiss() { - onDismiss() - } - - override fun choose(target: VideoComponent.Subtitle?) { - onChosen(target) - dismiss() - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/CountryImage.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/CountryImage.kt deleted file mode 100644 index 924eeb8f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/CountryImage.kt +++ /dev/null @@ -1,216 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.ImageResource -import java.util.* - -object CountryImage { - - fun getByCode(defaultCode: String, applyLocale: Boolean = true): ImageResource { - val code = if (applyLocale) { - val bestCode = defaultCode.split("[-_]".toRegex()).firstOrNull() ?: defaultCode - scopeCatching { - Locale.Builder().setLanguage(bestCode).build() - }.getOrNull()?.country ?: scopeCatching { - Locale.forLanguageTag(bestCode) - }.getOrNull()?.country ?: defaultCode - } else { - defaultCode - } - - return when { - code.equals("AE", true) -> SharedRes.images.AE - code.equals("AG", true) -> SharedRes.images.AG - code.equals("AL", true) -> SharedRes.images.AL - code.equals("AM", true) -> SharedRes.images.AM - code.equals("AR", true) -> SharedRes.images.AR - code.equals("AT", true) -> SharedRes.images.AT - code.equals("AU", true) -> SharedRes.images.AU - code.equals("AZ", true) -> SharedRes.images.AZ - - code.equals("BA", true) -> SharedRes.images.BA - code.equals("BB", true) -> SharedRes.images.BA - code.equals("BD", true) -> SharedRes.images.BD - code.equals("BE", true) -> SharedRes.images.BE - code.equals("BF", true) -> SharedRes.images.BF - code.equals("BG", true) -> SharedRes.images.BG - code.equals("BH", true) -> SharedRes.images.BH - code.equals("BI", true) -> SharedRes.images.BI - code.equals("BJ", true) -> SharedRes.images.BJ - code.equals("BO", true) -> SharedRes.images.BO - code.equals("BR", true) -> SharedRes.images.BR - code.equals("BS", true) -> SharedRes.images.BS - code.equals("BW", true) -> SharedRes.images.BW - code.equals("BY", true) -> SharedRes.images.BY - - code.equals("CA", true) -> SharedRes.images.CA - code.equals("CD", true) -> SharedRes.images.CD - code.equals("CF", true) -> SharedRes.images.CF - code.equals("CG", true) -> SharedRes.images.CG - code.equals("CH", true) -> SharedRes.images.CH - code.equals("CI", true) -> SharedRes.images.CI - code.equals("CK", true) -> SharedRes.images.CK - code.equals("CL", true) -> SharedRes.images.CL - code.equals("CM", true) -> SharedRes.images.CM - code.equals("CN", true) -> SharedRes.images.CN - code.equals("CO", true) -> SharedRes.images.CO - code.equals("CR", true) -> SharedRes.images.CR - code.equals("CTR", true) -> SharedRes.images.CTR - code.equals("CU", true) -> SharedRes.images.CU - code.equals("CV", true) -> SharedRes.images.CV - code.equals("CZ", true) -> SharedRes.images.CZ - - code.equals("DE", true) -> SharedRes.images.DE - code.equals("DJ", true) -> SharedRes.images.DJ - code.equals("DK", true) -> SharedRes.images.DK - code.equals("DO", true) -> SharedRes.images.COUNTRY_DO - - code.equals("EE", true) -> SharedRes.images.EE - code.equals("EG", true) -> SharedRes.images.EG - code.equals("ES", true) -> SharedRes.images.ES - code.equals("EU", true) || code.equals("Europe", true) -> SharedRes.images.EU - - code.equals("FI", true) -> SharedRes.images.COUNTRY_FI - code.equals("FJ", true) -> SharedRes.images.FJ - code.equals("FM", true) -> SharedRes.images.FM - code.equals("FR", true) -> SharedRes.images.FR - - code.equals("GA", true) -> SharedRes.images.GA - code.equals("GE", true) -> SharedRes.images.GE - code.equals("GH", true) -> SharedRes.images.GH - code.equals("GL", true) -> SharedRes.images.GL - code.equals("GM", true) -> SharedRes.images.GM - code.equals("GN", true) -> SharedRes.images.GN - code.equals("GR", true) -> SharedRes.images.GR - code.equals("GW", true) -> SharedRes.images.GW - code.equals("GY", true) -> SharedRes.images.GY - - code.equals("HK", true) -> SharedRes.images.HK - code.equals("HN", true) -> SharedRes.images.HN - code.equals("HR", true) -> SharedRes.images.HR - code.equals("HT", true) -> SharedRes.images.HT - code.equals("HU", true) -> SharedRes.images.HU - - code.equals("ID", true) -> SharedRes.images.ID - code.equals("IE", true) -> SharedRes.images.IE - code.equals("IL", true) -> SharedRes.images.IL - code.equals("IN", true) -> SharedRes.images.COUNTRY_IN - code.equals("IT", true) -> SharedRes.images.IT - - code.equals("JM", true) -> SharedRes.images.JM - code.equals("JO", true) -> SharedRes.images.JO - code.equals("JP", true) -> SharedRes.images.JP - - code.equals("KM", true) -> SharedRes.images.KM - code.equals("KN", true) -> SharedRes.images.KN - code.equals("KP", true) -> SharedRes.images.KP - code.equals("KRD", true) -> SharedRes.images.KRD - code.equals("KW", true) -> SharedRes.images.KW - - code.equals("LA", true) -> SharedRes.images.LA - code.equals("LI", true) -> SharedRes.images.LI - code.equals("LR", true) -> SharedRes.images.LR - code.equals("LT", true) -> SharedRes.images.LT - code.equals("LU", true) -> SharedRes.images.LU - code.equals("LV", true) -> SharedRes.images.LV - - code.equals("MA", true) -> SharedRes.images.MA - code.equals("MC", true) -> SharedRes.images.MC - code.equals("ME", true) -> SharedRes.images.ME - code.equals("MG", true) -> SharedRes.images.MG - code.equals("MH", true) -> SharedRes.images.MH - code.equals("MK", true) -> SharedRes.images.MK - code.equals("ML", true) -> SharedRes.images.ML - code.equals("MM", true) -> SharedRes.images.MM - code.equals("MR", true) -> SharedRes.images.MR - code.equals("MU", true) -> SharedRes.images.MU - code.equals("MV", true) -> SharedRes.images.MV - code.equals("MX", true) -> SharedRes.images.MX - code.equals("MY", true) -> SharedRes.images.MY - - code.equals("NA", true) -> SharedRes.images.NA - code.equals("NE", true) -> SharedRes.images.NE - code.equals("NG", true) -> SharedRes.images.NG - code.equals("NI", true) -> SharedRes.images.NI - code.equals("NKR", true) -> SharedRes.images.NKR - code.equals("NL", true) -> SharedRes.images.NL - code.equals("NO", true) -> SharedRes.images.NO - code.equals("NR", true) -> SharedRes.images.NR - code.equals("NU", true) -> SharedRes.images.NU - code.equals("NZ", true) -> SharedRes.images.NZ - - code.equals("PA", true) -> SharedRes.images.PA - code.equals("PE", true) -> SharedRes.images.PE - code.equals("PG", true) -> SharedRes.images.PG - code.equals("PH", true) -> SharedRes.images.PH - code.equals("PK", true) -> SharedRes.images.PK - code.equals("PL", true) -> SharedRes.images.PL - code.equals("PMR", true) -> SharedRes.images.PMR - code.equals("PS", true) -> SharedRes.images.PS - code.equals("PT", true) -> SharedRes.images.PT - code.equals("PW", true) -> SharedRes.images.PW - - code.equals("QA", true) -> SharedRes.images.QA - - code.equals("RO", true) -> SharedRes.images.RO - code.equals("RS", true) -> SharedRes.images.RS - code.equals("RU", true) -> SharedRes.images.RU - - code.equals("SE", true) -> SharedRes.images.SE - code.equals("SG", true) -> SharedRes.images.SG - code.equals("SH", true) -> SharedRes.images.SH - code.equals("SI", true) -> SharedRes.images.SI - code.equals("SK", true) -> SharedRes.images.SK - code.equals("SO", true) -> SharedRes.images.SO - code.equals("ST", true) -> SharedRes.images.ST - code.equals("SY", true) -> SharedRes.images.SY - - code.equals("TH", true) -> SharedRes.images.TH - code.equals("TL", true) -> SharedRes.images.TL - code.equals("TO", true) -> SharedRes.images.TO - code.equals("TR", true) -> SharedRes.images.TR - code.equals("TW", true) -> SharedRes.images.TW - - code.equals("UA", true) -> SharedRes.images.UA - code.equals("UK", true) -> SharedRes.images.UK - code.equals("US", true) -> SharedRes.images.US - code.equals("UY", true) -> SharedRes.images.UY - code.equals("UZ", true) -> SharedRes.images.UZ - - code.equals("VA", true) -> SharedRes.images.VA - code.equals("VE", true) -> SharedRes.images.VE - code.equals("VN", true) -> SharedRes.images.VN - - code.equals("WS", true) -> SharedRes.images.WS - - code.equals("XK", true) -> SharedRes.images.XK - - code.equals("YE", true) -> SharedRes.images.YE - - else -> if (applyLocale) getByCode(defaultCode, false) else SharedRes.images.COUNTRY_UNKNOWN - } - } - - fun getByFlag(code: String): List { - val defaultByCode = getByCode(code) - return if (defaultByCode == SharedRes.images.COUNTRY_UNKNOWN) { - when { - code.equals("DES", true) -> listOf( - SharedRes.images.US, - SharedRes.images.DE - ) - code.equals("EN", true) -> listOf( - SharedRes.images.US - ) - code.equals("JPS", true) -> listOf( - SharedRes.images.JP, - SharedRes.images.US - ) - else -> listOf(defaultByCode) - } - } else { - listOf(defaultByCode) - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/MaterialSymbols.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/MaterialSymbols.kt deleted file mode 100644 index 8615fa1a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/MaterialSymbols.kt +++ /dev/null @@ -1,39 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import androidx.compose.material3.LocalContentColor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.* -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.drawPathFromSvgData - -data object MaterialSymbols { - @Composable - fun rememberDeployedCodeAlert(fillColor: Color = LocalContentColor.current): ImageVector { - return remember { - ImageVector.Builder( - name = "deployed_code_alert", - defaultWidth = 24.dp, - defaultHeight = 24.dp, - viewportWidth = 24.0f, - viewportHeight = 24.0f - ).apply { - path( - fill = SolidColor(fillColor), - fillAlpha = 1f, - stroke = null, - strokeAlpha = 1f, - strokeLineWidth = 1.0f, - strokeLineCap = StrokeCap.Butt, - strokeLineJoin = StrokeJoin.Miter, - strokeLineMiter = 1f, - pathFillType = PathFillType.NonZero - ) { - drawPathFromSvgData("M11 2.775C11.3167 2.59167 11.65 2.5 12 2.5C12.35 2.5 12.6833 2.59167 13 2.775L20 6.8C20.3167 6.98333 20.5625 7.225 20.7375 7.525C20.9125 7.825 21 8.15833 21 8.525V12.775C20.7 12.5583 20.3833 12.3708 20.05 12.2125C19.7167 12.0542 19.3667 11.9167 19 11.8V9.6L15.4 11.675C14.4667 11.9083 13.6208 12.3042 12.8625 12.8625C12.1042 13.4208 11.4833 14.1 11 14.9V13.075L5 9.6V16.45L10.05 19.375C10.1167 19.9083 10.2417 20.4208 10.425 20.9125C10.6083 21.4042 10.8417 21.8667 11.125 22.3C11.0917 22.2833 11.0708 22.2708 11.0625 22.2625C11.0542 22.2542 11.0333 22.2417 11 22.225L4 18.2C3.68333 18.0167 3.4375 17.775 3.2625 17.475C3.0875 17.175 3 16.8417 3 16.475V8.525C3 8.15833 3.0875 7.825 3.2625 7.525C3.4375 7.225 3.68333 6.98333 4 6.8L11 2.775ZM12 4.5L6.075 7.925L12 11.35L17.925 7.925L12 4.5ZM17 23.5C15.6167 23.5 14.4375 23.0125 13.4625 22.0375C12.4875 21.0625 12 19.8833 12 18.5C12 17.1167 12.4875 15.9375 13.4625 14.9625C14.4375 13.9875 15.6167 13.5 17 13.5C18.3833 13.5 19.5625 13.9875 20.5375 14.9625C21.5125 15.9375 22 17.1167 22 18.5C22 19.8833 21.5125 21.0625 20.5375 22.0375C19.5625 23.0125 18.3833 23.5 17 23.5ZM17 21.5C17.1333 21.5 17.25 21.45 17.35 21.35C17.45 21.25 17.5 21.1333 17.5 21C17.5 20.8667 17.45 20.75 17.35 20.65C17.25 20.55 17.1333 20.5 17 20.5C16.8667 20.5 16.75 20.55 16.65 20.65C16.55 20.75 16.5 20.8667 16.5 21C16.5 21.1333 16.55 21.25 16.65 21.35C16.75 21.45 16.8667 21.5 17 21.5ZM16.5 19.5H17.5V15.5H16.5V19.5Z") - } - }.build() - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Shapes.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Shapes.kt deleted file mode 100644 index f96a5f7a..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Shapes.kt +++ /dev/null @@ -1,49 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.CornerBasedShape -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -fun TopLeftBottomRightRoundedShape(baseShape: CornerBasedShape = CircleShape, otherSideRounding: Dp = 2.dp) = baseShape.copy( - topEnd = CornerSize(otherSideRounding), - bottomStart = CornerSize(otherSideRounding) -) - -fun TopRightBottomLeftRoundedShape(baseShape: CornerBasedShape = CircleShape, otherSideRounding: Dp = 2.dp) = baseShape.copy( - topStart = CornerSize(otherSideRounding), - bottomEnd = CornerSize(otherSideRounding) -) - -val BottomLeftRoundedShape = CircleShape.copy( - topStart = CornerSize(2.dp), - topEnd = CornerSize(2.dp), - bottomEnd = CornerSize(2.dp) -) - -val BottomRightRoundedShape = CircleShape.copy( - topStart = CornerSize(2.dp), - topEnd = CornerSize(2.dp), - bottomStart = CornerSize(2.dp) -) - -fun LeftRoundedShape(rightSideRounding: Dp = 2.dp): Shape { - return CircleShape.copy( - topEnd = CornerSize(rightSideRounding), - bottomEnd = CornerSize(rightSideRounding) - ) -} - -fun RightRoundedShape(leftSideRounding: Dp = 2.dp): Shape { - return CircleShape.copy( - topStart = CornerSize(leftSideRounding), - bottomStart = CornerSize(leftSideRounding) - ) -} - -fun MiddleRoundedShape(sideRounding: Dp = 2.dp): Shape { - return RoundedCornerShape(sideRounding) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Theme.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Theme.kt deleted file mode 100644 index 90f57f17..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Theme.kt +++ /dev/null @@ -1,155 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.asFont - -@Composable -fun ManropeFontFamily(): FontFamily { - val fonts = listOfNotNull( - SharedRes.fonts.Manrope.`extra-light`.asFont(FontWeight.ExtraLight), - SharedRes.fonts.Manrope.`extra-light-italic`.asFont(FontWeight.ExtraLight, FontStyle.Italic), - - SharedRes.fonts.Manrope.light.asFont(FontWeight.Light), - SharedRes.fonts.Manrope.`light-italic`.asFont(FontWeight.Light, FontStyle.Italic), - - SharedRes.fonts.Manrope.regular.asFont(FontWeight.Normal), - SharedRes.fonts.Manrope.`regular-italic`.asFont(FontWeight.Normal, FontStyle.Italic), - - SharedRes.fonts.Manrope.medium.asFont(FontWeight.Medium), - SharedRes.fonts.Manrope.`medium-italic`.asFont(FontWeight.Medium, FontStyle.Italic), - - SharedRes.fonts.Manrope.`semi-bold`.asFont(FontWeight.SemiBold), - SharedRes.fonts.Manrope.`semi-bold-italic`.asFont(FontWeight.SemiBold, FontStyle.Italic), - - SharedRes.fonts.Manrope.bold.asFont(FontWeight.Bold), - SharedRes.fonts.Manrope.`bold-italic`.asFont(FontWeight.Bold, FontStyle.Italic), - - SharedRes.fonts.Manrope.`extra-bold`.asFont(FontWeight.ExtraBold), - SharedRes.fonts.Manrope.`extra-bold-italic`.asFont(FontWeight.ExtraBold, FontStyle.Italic), - ) - - return FontFamily(fonts) -} - -@Composable -fun ManropeTypography(): Typography { - val fontFamily = ManropeFontFamily() - - return remember(fontFamily) { - Typography( - displayLarge = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 57.sp, - lineHeight = 64.0.sp, - letterSpacing = (-0.2).sp, - ), - displayMedium = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 45.sp, - lineHeight = 52.0.sp, - letterSpacing = 0.0.sp - ), - displaySmall = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 36.sp, - lineHeight = 44.0.sp, - letterSpacing = 0.0.sp - ), - headlineLarge = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 32.sp, - lineHeight = 40.0.sp, - letterSpacing = 0.0.sp - ), - headlineMedium = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 28.sp, - lineHeight = 36.0.sp, - letterSpacing = 0.0.sp - ), - headlineSmall = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 24.sp, - lineHeight = 32.0.sp, - letterSpacing = 0.0.sp - ), - titleLarge = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.0.sp, - letterSpacing = 0.0.sp - ), - titleMedium = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 24.0.sp, - letterSpacing = 0.2.sp - ), - titleSmall = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.0.sp, - letterSpacing = 0.1.sp - ), - bodyLarge = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.0.sp, - letterSpacing = 0.5.sp - ), - bodyMedium = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 20.0.sp, - letterSpacing = 0.2.sp - ), - bodySmall = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Normal, - fontSize = 12.sp, - lineHeight = 16.0.sp, - letterSpacing = 0.4.sp, - ), - labelLarge = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.0.sp, - letterSpacing = 0.1.sp - ), - labelMedium = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Medium, - fontSize = 12.sp, - lineHeight = 16.0.sp, - letterSpacing = 0.5.sp - ), - labelSmall = TextStyle( - fontFamily = fontFamily, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.0.sp, - letterSpacing = 0.5.sp - ) - ) - } -} diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/shape/DiagonalShape.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/shape/DiagonalShape.kt deleted file mode 100644 index f7a4b8a5..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/shape/DiagonalShape.kt +++ /dev/null @@ -1,108 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme.shape - -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Outline -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.LayoutDirection -import kotlin.math.PI -import kotlin.math.abs -import kotlin.math.tan - -data class DiagonalShape( - internal val angle: Float = 0F, - internal val position: POSITION = POSITION.TOP -) : Shape { - - private val direction: DIRECTION - get() = if (angle > 0) DIRECTION.LEFT else DIRECTION.RIGHT - - override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { - return Outline.Generic(Path().apply { - val angleAbs = abs(angle) - val perpendicularHeight = (size.width * tan(angleAbs / 180 * PI)).toFloat() - - when (position) { - is POSITION.START -> { - when (direction) { - is DIRECTION.LEFT -> { - this.moveTo(perpendicularHeight, 0F) - this.lineTo(size.width, 0F) - this.lineTo(size.width, size.height) - this.lineTo(0F, size.height) - } - is DIRECTION.RIGHT -> { - this.moveTo(0F, 0F) - this.lineTo(size.width, 0F) - this.lineTo(size.width, size.height) - this.lineTo(perpendicularHeight, size.height) - } - } - } - is POSITION.TOP -> { - when (direction) { - is DIRECTION.LEFT -> { - this.moveTo(size.width, size.height) - this.lineTo(size.width, perpendicularHeight) - this.lineTo(0F, 0F) - this.lineTo(0F, size.height) - } - is DIRECTION.RIGHT -> { - this.moveTo(size.width, size.height) - this.lineTo(size.width, 0F) - this.lineTo(0F, perpendicularHeight) - this.lineTo(0F, size.height) - } - } - } - is POSITION.END -> { - when (direction) { - is DIRECTION.LEFT -> { - this.moveTo(0F, 0F) - this.lineTo(size.width, 0F) - this.lineTo(size.width - perpendicularHeight, size.height) - this.lineTo(0F, size.height) - } - is DIRECTION.RIGHT -> { - this.moveTo(0F, 0F) - this.lineTo(size.width - perpendicularHeight, 0F) - this.lineTo(size.width, size.height) - this.lineTo(0F, size.height) - } - } - } - is POSITION.BOTTOM -> { - when (direction) { - is DIRECTION.LEFT -> { - this.moveTo(0F, 0F) - this.lineTo(size.width, 0F) - this.lineTo(size.width, size.height - perpendicularHeight) - this.lineTo(0F, size.height) - } - is DIRECTION.RIGHT -> { - this.moveTo(size.width, size.height) - this.lineTo(0F, size.height - perpendicularHeight) - this.lineTo(0F, 0F) - this.lineTo(size.width, 0F) - } - } - } - } - - this.close() - }) - } - - sealed interface POSITION { - data object START : POSITION - data object TOP : POSITION - data object END : POSITION - data object BOTTOM : POSITION - } - - private sealed interface DIRECTION { - data object LEFT : DIRECTION - data object RIGHT : DIRECTION - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/assets/icns/launcher.icns b/app/shared/src/commonMain/resources/MR/assets/icns/launcher.icns deleted file mode 100644 index f110b7d5..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/icns/launcher.icns and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_128.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_128.ico deleted file mode 100644 index f82b15dd..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_128.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_16.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_16.ico deleted file mode 100644 index 86b2aefc..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_16.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_32.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_32.ico deleted file mode 100644 index 6be5eb4b..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_32.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_48.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_48.ico deleted file mode 100644 index a6f8d510..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_48.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_64.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_64.ico deleted file mode 100644 index f4450f4a..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_64.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_96.ico b/app/shared/src/commonMain/resources/MR/assets/ico/launcher_96.ico deleted file mode 100644 index d9cf7e89..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/ico/launcher_96.ico and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_128.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_128.png deleted file mode 100644 index 74c345a4..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_128.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_16.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_16.png deleted file mode 100644 index 1b1b6c21..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_16.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_32.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_32.png deleted file mode 100644 index 37f6ed19..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_32.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_48.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_48.png deleted file mode 100644 index df3f0d9f..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_48.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_64.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_64.png deleted file mode 100644 index 504550cd..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_64.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/png/launcher_96.png b/app/shared/src/commonMain/resources/MR/assets/png/launcher_96.png deleted file mode 100644 index 013508c2..00000000 Binary files a/app/shared/src/commonMain/resources/MR/assets/png/launcher_96.png and /dev/null differ diff --git a/app/shared/src/commonMain/resources/MR/assets/scrape_hoster_cef.js b/app/shared/src/commonMain/resources/MR/assets/scrape_hoster_cef.js deleted file mode 100644 index 694ef6c9..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/scrape_hoster_cef.js +++ /dev/null @@ -1,29 +0,0 @@ -const hosterTabs = document.getElementsByClassName("hoster-tabs"); -let activeHoster = null; -if ((hosterTabs !== null && hosterTabs !== undefined) && hosterTabs.length > 0) { - const activeTabs = hosterTabs[0].getElementsByClassName("active"); - if ((activeTabs !== null && activeTabs !== undefined) && activeTabs.length > 0) { - const hosters = activeTabs[0].getElementsByTagName("a"); - if ((hosters !== null && hosters !== undefined) && hosters.length > 0) { - activeHoster = hosters[0].getAttribute("href"); - } - } -} - -const hosterPlayers = document.getElementsByClassName("hoster-player"); -if ((hosterPlayers !== null && hosterPlayers !== undefined) && hosterPlayers.length > 0) { - const player = hosterPlayers[0]; - const links = player.getElementsByTagName("a"); - if ((links !== null && links !== undefined) && links.length > 0) { - const link = links[0].getAttribute("href"); - return JSON.stringify({href: activeHoster, url: link, embed: false}); - } else { - const frames = player.getElementsByTagName("iframe"); - if ((frames !== null && frames !== undefined) && frames.length > 0) { - const link = frames[0].getAttribute("src"); - return JSON.stringify({href: activeHoster, url: link, embed: true}); - } else { - return null; - } - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_128.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_128.svg deleted file mode 100644 index 8bb1be76..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_128.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_16.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_16.svg deleted file mode 100644 index 0b0b295a..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_16.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_32.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_32.svg deleted file mode 100644 index 039bcd5c..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_32.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_48.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_48.svg deleted file mode 100644 index 26324689..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_48.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_64.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_64.svg deleted file mode 100644 index 55fd23a6..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_64.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_96.svg b/app/shared/src/commonMain/resources/MR/assets/svg/launcher_96.svg deleted file mode 100644 index 5a65dbd3..00000000 --- a/app/shared/src/commonMain/resources/MR/assets/svg/launcher_96.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/base/strings.xml b/app/shared/src/commonMain/resources/MR/base/strings.xml deleted file mode 100644 index d61e218d..00000000 --- a/app/shared/src/commonMain/resources/MR/base/strings.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - Burning-Series - Home - Favorites - Favorite - Search - Newest Episodes - Newest Series - Retry - Loading newest episodes and series, please wait - Error while loading newest episodes and series - Read More - Read Less - Loading series information, please wait - Error while loading series information - Season %s - Back - Restart required - The application needs to be restarted to function properly. - Restart - Downloading - Downloading required packages for your platform, please wait - Loading search information, please wait - Error while loading search information - Select Season - Select Language - Confirm - Close - Clear - Stream unavailable - There is no stream available, you can activate them now in this app or use using the browser extension on your PC. - Activate - Activate episodes by loading them once - Later - Browser initializing, please wait - Successfully activated hoster - You can go back now or continue browsing and activate some more. - Error activating hoster - Could not activate hoster, please try again later. - Watch - Rewind - Forward - Pause - Play - Fullscreen - Exit Fullscreen - %s - %s - Unable to activate with your current network as the website is unreachable. - Saving - Select Subtitle - Subtitles off - Notifications - This app uses notifications for controlling videos. - Seems like you didn\'t grant the notification permission last time.\nIt is required to have full control over your videos. - Grant - Deny - Video - Notifications of this type provide more control over your player. - Casting - Selected - Available - Disconnect - VLC is required to play videos on your PC.\nMake sure to only download and install the official package from videolan.org or your system package manager. - Download - Activate episode - Do you want to activate this episode now? - New version available - A new version is available "%s".\nYou should check it out! - GitHub - Sekret unavailable - Some features do not work currently.\nYou should restart the app and check if this issue persists. - Muted - Unmuted - Loading series - You should set a custom DNS to make sure you can access all features. - Yes - Sponsor - Maintaining this project is time and money consuming, please consider contributing financially to support it\'s upkeep. - Polar - Patreon - PayPal - The amount doesn\'t matter, whether it\'s 1€ or 10€, just choose what you can afford and you\'re most comfortable with :) - Free Support - You\'re unable to support me financially? No problem, here are some projects you can check out instead. - PulZ - Gaming and E-Sport content - AniFlow - Sync your Anime watch progress with AniList. - Enable - Open Links - You are not protected against fake domains right now! - Additionally you can view Burning-Series search results directly in this app then. - Make sure to check the option to open supported links and add every link below. - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/de/strings.xml b/app/shared/src/commonMain/resources/MR/de/strings.xml deleted file mode 100644 index 7db1e577..00000000 --- a/app/shared/src/commonMain/resources/MR/de/strings.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - Burning-Series - Start - Favoriten - Favorit - Suchen - Neuste Episoden - Neuste Serien - Wiederholen - Neuste Episoden und Serien werden geladen, bitte warten - Fehler beim Laden neuer Episoden und Serien - Mehr lesen - Weniger lesen - Serieninformationen werden geladen, bitte warten - Fehler beim Laden von Serieninformationen - Staffel %s - Zurück - Neustart erforderlich - Die App muss neu gestartet werden, damit sie ordnungsgemäß funktioniert. - Neustarten - Wird heruntergeladen - Erforderliche Pakete für dein System werden heruntergeladen, bitte warten - Suchinformationen werden geladen, bitte warten - Fehler beim Laden von Suchinformationen - Staffel auswählen - Sprache auswählen - Bestätigen - Schließen - Löschen - Stream nicht verfügbar - Kein Stream verfügbar, du kannst jetzt welche in der App aktivieren oder die Browser-Extension auf dem PC verwenden. - Aktivieren - Aktiviere Episoden, indem du sie einmal lädst - Später - Browser wird initialisiert, bitte warten - Hoster erfolgreich aktiviert - Du kannst jetzt zurückgehen oder weitermachen und mehr aktivieren. - Fehler beim Aktivieren des Hosters - Hoster konnte nicht aktiviert werden, bitte versuche es später nochmal. - Ansehen - Zurück - Vor - Pause - Abspielen - Vollbild - Vollbild beenden - %s - %s - Aktivieren mit deinem aktuellen Netzwerk nicht möglich, da die Website nicht erreichbar ist. - Speichern - Untertitel auswählen - Untertitel aus - Benachrichtigungen - Diese App verwendet Benachrichtigungen zum Kontrollieren von Videos. - Anscheinend hast du die Benachrichtigungsberechtigung beim letzten Mal nicht erlaubt.\nSie ist erforderlich um volle Kontrolle über Videos zu haben. - Erlauben - Verweigern - Video - Benachrichtigungen dieser Art geben dir mehr Kontrolle über Videos. - Casting - Ausgewählt - Verfügbar - Unterbrechen - VLC muss auf deinem PC installiert sein, um Videos abzuspielen.\nStelle sicher, dass du nur das offizielle Paket von videolan.org oder deinem Systempaketmanager herunterlädst und installierst. - Herunterladen - Episode aktivieren - Willst du diese Episode jetzt aktivieren? - Neue Version verfügbar - Eine neue Version ist verfügbar "%s".\nDas solltest du dir ansehen! - GitHub - Sekret nicht verfügbar - Einige Funktionen sind momentan nicht verfügbar.\nDu solltest die App neu starten und prüfen, ob das Problem bestehen bleibt. - Stumm - Nicht stumm - Lade Serie - Du solltest einen DNS-Server einrichten, um alle Funktionen zuverlässig nutzen zu können. - Ja - Sponsern - Das Projekt zu Warten ist zeit- und kostenintensiv. Bitte denk darüber nach, einen finanziellen Beitrag zu leisten, um das Projekt am Leben zu halten. - Polar - Patreon - PayPal - Der Betrag spielt keine Rolle, ob 1€ oder 10€, wähl einfach selbst wie viel du dir leisten kannst und womit du dich am wohlsten fühlst :) - Kostenlose Unterstützung - Du kannst dir eine finanzielle Unterstützung nicht leisten? Kein Problem, sieh dir stattdessen doch einfach diese Projekte an. - PulZ - Gaming und E-Sport Infos - AniFlow - Synchronisier deinen Anime Fortschritt mit AniList. - Aktivieren - Links öffnen - Du bist momentan nicht vor Fake-Seiten geschützt! - Zusätzlich kannst du dann Burning-Series Suchergebnisse in der App sehen. - Aktiviere einfach die Option unterstützte Links zu öffnen und füge diese darunter hinzu. - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/AE.svg b/app/shared/src/commonMain/resources/MR/images/AE.svg deleted file mode 100644 index e7181458..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AE.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AG.svg b/app/shared/src/commonMain/resources/MR/images/AG.svg deleted file mode 100644 index 3e4b89d7..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AG.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AL.svg b/app/shared/src/commonMain/resources/MR/images/AL.svg deleted file mode 100644 index 43e470f4..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AL.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AM.svg b/app/shared/src/commonMain/resources/MR/images/AM.svg deleted file mode 100644 index a49a9cfe..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AM.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AR.svg b/app/shared/src/commonMain/resources/MR/images/AR.svg deleted file mode 100644 index b0b69f13..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AR.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AT.svg b/app/shared/src/commonMain/resources/MR/images/AT.svg deleted file mode 100644 index 24c47c48..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AT.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AU.svg b/app/shared/src/commonMain/resources/MR/images/AU.svg deleted file mode 100644 index 6773a25e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AU.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/AZ.svg b/app/shared/src/commonMain/resources/MR/images/AZ.svg deleted file mode 100644 index 16edab0c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/AZ.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BA.svg b/app/shared/src/commonMain/resources/MR/images/BA.svg deleted file mode 100644 index 5ba09f45..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BA.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BB.svg b/app/shared/src/commonMain/resources/MR/images/BB.svg deleted file mode 100644 index bf942f91..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BB.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BD.svg b/app/shared/src/commonMain/resources/MR/images/BD.svg deleted file mode 100644 index 489ee24b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BD.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BE.svg b/app/shared/src/commonMain/resources/MR/images/BE.svg deleted file mode 100644 index b81738af..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BE.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BF.svg b/app/shared/src/commonMain/resources/MR/images/BF.svg deleted file mode 100644 index 405f2008..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BF.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BG.svg b/app/shared/src/commonMain/resources/MR/images/BG.svg deleted file mode 100644 index 67d08d82..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BG.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BH.svg b/app/shared/src/commonMain/resources/MR/images/BH.svg deleted file mode 100644 index 29200345..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BH.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BI.svg b/app/shared/src/commonMain/resources/MR/images/BI.svg deleted file mode 100644 index 542c4ac2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BI.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BJ.svg b/app/shared/src/commonMain/resources/MR/images/BJ.svg deleted file mode 100644 index a896eff0..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BJ.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BO.svg b/app/shared/src/commonMain/resources/MR/images/BO.svg deleted file mode 100644 index 6808c26c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BO.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BR.svg b/app/shared/src/commonMain/resources/MR/images/BR.svg deleted file mode 100644 index 709af746..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BR.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BS.svg b/app/shared/src/commonMain/resources/MR/images/BS.svg deleted file mode 100644 index dd4534a7..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BS.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BW.svg b/app/shared/src/commonMain/resources/MR/images/BW.svg deleted file mode 100644 index c22697f9..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BW.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/BY.svg b/app/shared/src/commonMain/resources/MR/images/BY.svg deleted file mode 100644 index 63e70a12..00000000 --- a/app/shared/src/commonMain/resources/MR/images/BY.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CA.svg b/app/shared/src/commonMain/resources/MR/images/CA.svg deleted file mode 100644 index c9db3c7d..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CA.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CD.svg b/app/shared/src/commonMain/resources/MR/images/CD.svg deleted file mode 100644 index 60435d86..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CD.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CF.svg b/app/shared/src/commonMain/resources/MR/images/CF.svg deleted file mode 100644 index 6733395a..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CF.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CG.svg b/app/shared/src/commonMain/resources/MR/images/CG.svg deleted file mode 100644 index 0e8197fd..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CG.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CH.svg b/app/shared/src/commonMain/resources/MR/images/CH.svg deleted file mode 100644 index 10934ef5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CH.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CI.svg b/app/shared/src/commonMain/resources/MR/images/CI.svg deleted file mode 100644 index 39d68cb9..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CI.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CK.svg b/app/shared/src/commonMain/resources/MR/images/CK.svg deleted file mode 100644 index 7a12eb0b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CK.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CL.svg b/app/shared/src/commonMain/resources/MR/images/CL.svg deleted file mode 100644 index 221e5fe9..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CL.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CM.svg b/app/shared/src/commonMain/resources/MR/images/CM.svg deleted file mode 100644 index 3d404801..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CM.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CN.svg b/app/shared/src/commonMain/resources/MR/images/CN.svg deleted file mode 100644 index d2cc4581..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CN.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CO.svg b/app/shared/src/commonMain/resources/MR/images/CO.svg deleted file mode 100644 index 109bc16f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CO.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/COUNTRY_DO.svg b/app/shared/src/commonMain/resources/MR/images/COUNTRY_DO.svg deleted file mode 100644 index 55e86ba5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/COUNTRY_DO.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/COUNTRY_FI.svg b/app/shared/src/commonMain/resources/MR/images/COUNTRY_FI.svg deleted file mode 100644 index 0ca7e65c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/COUNTRY_FI.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/COUNTRY_IN.svg b/app/shared/src/commonMain/resources/MR/images/COUNTRY_IN.svg deleted file mode 100644 index d5187a59..00000000 --- a/app/shared/src/commonMain/resources/MR/images/COUNTRY_IN.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CR.svg b/app/shared/src/commonMain/resources/MR/images/CR.svg deleted file mode 100644 index 731432ed..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CR.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CTR.svg b/app/shared/src/commonMain/resources/MR/images/CTR.svg deleted file mode 100644 index 6dea05d0..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CTR.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CU.svg b/app/shared/src/commonMain/resources/MR/images/CU.svg deleted file mode 100644 index 951cb16a..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CU.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CV.svg b/app/shared/src/commonMain/resources/MR/images/CV.svg deleted file mode 100644 index 3028a85e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CV.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/CZ.svg b/app/shared/src/commonMain/resources/MR/images/CZ.svg deleted file mode 100644 index 2b532d10..00000000 --- a/app/shared/src/commonMain/resources/MR/images/CZ.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/DJ.svg b/app/shared/src/commonMain/resources/MR/images/DJ.svg deleted file mode 100644 index 6ca0f1f5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/DJ.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/DK.svg b/app/shared/src/commonMain/resources/MR/images/DK.svg deleted file mode 100644 index 1aaa65c5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/DK.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/EE.svg b/app/shared/src/commonMain/resources/MR/images/EE.svg deleted file mode 100644 index 52b14f3f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/EE.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/EG.svg b/app/shared/src/commonMain/resources/MR/images/EG.svg deleted file mode 100644 index 319475b4..00000000 --- a/app/shared/src/commonMain/resources/MR/images/EG.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/ES.svg b/app/shared/src/commonMain/resources/MR/images/ES.svg deleted file mode 100644 index 2a5bfb25..00000000 --- a/app/shared/src/commonMain/resources/MR/images/ES.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/EU.svg b/app/shared/src/commonMain/resources/MR/images/EU.svg deleted file mode 100644 index 1c58d793..00000000 --- a/app/shared/src/commonMain/resources/MR/images/EU.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/FJ.svg b/app/shared/src/commonMain/resources/MR/images/FJ.svg deleted file mode 100644 index 9b09cd44..00000000 --- a/app/shared/src/commonMain/resources/MR/images/FJ.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/FM.svg b/app/shared/src/commonMain/resources/MR/images/FM.svg deleted file mode 100644 index db3e5c0b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/FM.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/FR.svg b/app/shared/src/commonMain/resources/MR/images/FR.svg deleted file mode 100644 index 3c231a47..00000000 --- a/app/shared/src/commonMain/resources/MR/images/FR.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GA.svg b/app/shared/src/commonMain/resources/MR/images/GA.svg deleted file mode 100644 index 938733d1..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GA.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GE.svg b/app/shared/src/commonMain/resources/MR/images/GE.svg deleted file mode 100644 index f9d596e1..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GE.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GH.svg b/app/shared/src/commonMain/resources/MR/images/GH.svg deleted file mode 100644 index 03ace555..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GH.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GL.svg b/app/shared/src/commonMain/resources/MR/images/GL.svg deleted file mode 100644 index 5089c39b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GL.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GM.svg b/app/shared/src/commonMain/resources/MR/images/GM.svg deleted file mode 100644 index 7a7ec13e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GM.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GN.svg b/app/shared/src/commonMain/resources/MR/images/GN.svg deleted file mode 100644 index b720a6e3..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GN.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GR.svg b/app/shared/src/commonMain/resources/MR/images/GR.svg deleted file mode 100644 index cb4a59e6..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GR.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GW.svg b/app/shared/src/commonMain/resources/MR/images/GW.svg deleted file mode 100644 index 03431d1e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GW.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/GY.svg b/app/shared/src/commonMain/resources/MR/images/GY.svg deleted file mode 100644 index ab5085fb..00000000 --- a/app/shared/src/commonMain/resources/MR/images/GY.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/HK.svg b/app/shared/src/commonMain/resources/MR/images/HK.svg deleted file mode 100644 index cf756ad9..00000000 --- a/app/shared/src/commonMain/resources/MR/images/HK.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/HN.svg b/app/shared/src/commonMain/resources/MR/images/HN.svg deleted file mode 100644 index c1ae2e43..00000000 --- a/app/shared/src/commonMain/resources/MR/images/HN.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/HR.svg b/app/shared/src/commonMain/resources/MR/images/HR.svg deleted file mode 100644 index 75db5a90..00000000 --- a/app/shared/src/commonMain/resources/MR/images/HR.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/HT.svg b/app/shared/src/commonMain/resources/MR/images/HT.svg deleted file mode 100644 index 42783a5d..00000000 --- a/app/shared/src/commonMain/resources/MR/images/HT.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/HU.svg b/app/shared/src/commonMain/resources/MR/images/HU.svg deleted file mode 100644 index 90794092..00000000 --- a/app/shared/src/commonMain/resources/MR/images/HU.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/ID.svg b/app/shared/src/commonMain/resources/MR/images/ID.svg deleted file mode 100644 index 5f3a90b6..00000000 --- a/app/shared/src/commonMain/resources/MR/images/ID.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/IE.svg b/app/shared/src/commonMain/resources/MR/images/IE.svg deleted file mode 100644 index ccc1a23c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/IE.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/IL.svg b/app/shared/src/commonMain/resources/MR/images/IL.svg deleted file mode 100644 index d7426d11..00000000 --- a/app/shared/src/commonMain/resources/MR/images/IL.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/IT.svg b/app/shared/src/commonMain/resources/MR/images/IT.svg deleted file mode 100644 index 907f03a3..00000000 --- a/app/shared/src/commonMain/resources/MR/images/IT.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/JM.svg b/app/shared/src/commonMain/resources/MR/images/JM.svg deleted file mode 100644 index 60a9b7d4..00000000 --- a/app/shared/src/commonMain/resources/MR/images/JM.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/JO.svg b/app/shared/src/commonMain/resources/MR/images/JO.svg deleted file mode 100644 index 6a8128f3..00000000 --- a/app/shared/src/commonMain/resources/MR/images/JO.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/KM.svg b/app/shared/src/commonMain/resources/MR/images/KM.svg deleted file mode 100644 index 32219c5f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/KM.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/KN.svg b/app/shared/src/commonMain/resources/MR/images/KN.svg deleted file mode 100644 index 6e3f7a7d..00000000 --- a/app/shared/src/commonMain/resources/MR/images/KN.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/KP.svg b/app/shared/src/commonMain/resources/MR/images/KP.svg deleted file mode 100644 index 1a0fa4c2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/KP.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/KRD.svg b/app/shared/src/commonMain/resources/MR/images/KRD.svg deleted file mode 100644 index 03c73cc8..00000000 --- a/app/shared/src/commonMain/resources/MR/images/KRD.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/KW.svg b/app/shared/src/commonMain/resources/MR/images/KW.svg deleted file mode 100644 index 2a172a7f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/KW.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LA.svg b/app/shared/src/commonMain/resources/MR/images/LA.svg deleted file mode 100644 index 1a7cbe4b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LA.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LI.svg b/app/shared/src/commonMain/resources/MR/images/LI.svg deleted file mode 100644 index 71249a7b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LI.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LR.svg b/app/shared/src/commonMain/resources/MR/images/LR.svg deleted file mode 100644 index 9e7216fc..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LR.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LT.svg b/app/shared/src/commonMain/resources/MR/images/LT.svg deleted file mode 100644 index 50f39faa..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LT.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LU.svg b/app/shared/src/commonMain/resources/MR/images/LU.svg deleted file mode 100644 index a4e3e25e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LU.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/LV.svg b/app/shared/src/commonMain/resources/MR/images/LV.svg deleted file mode 100644 index a6a7507b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/LV.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MA.svg b/app/shared/src/commonMain/resources/MR/images/MA.svg deleted file mode 100644 index 954fc6a0..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MA.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MC.svg b/app/shared/src/commonMain/resources/MR/images/MC.svg deleted file mode 100644 index 398c498a..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MC.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/ME.svg b/app/shared/src/commonMain/resources/MR/images/ME.svg deleted file mode 100644 index c3732f36..00000000 --- a/app/shared/src/commonMain/resources/MR/images/ME.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MG.svg b/app/shared/src/commonMain/resources/MR/images/MG.svg deleted file mode 100644 index d75e7311..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MG.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MH.svg b/app/shared/src/commonMain/resources/MR/images/MH.svg deleted file mode 100644 index 5b3f4410..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MH.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MK.svg b/app/shared/src/commonMain/resources/MR/images/MK.svg deleted file mode 100644 index 27754550..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MK.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/ML.svg b/app/shared/src/commonMain/resources/MR/images/ML.svg deleted file mode 100644 index 7c00693d..00000000 --- a/app/shared/src/commonMain/resources/MR/images/ML.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MM.svg b/app/shared/src/commonMain/resources/MR/images/MM.svg deleted file mode 100644 index f7dfebe7..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MM.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MR.svg b/app/shared/src/commonMain/resources/MR/images/MR.svg deleted file mode 100644 index 106db7d8..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MR.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MT.svg b/app/shared/src/commonMain/resources/MR/images/MT.svg deleted file mode 100644 index 2ddf5ebe..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MT.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MU.svg b/app/shared/src/commonMain/resources/MR/images/MU.svg deleted file mode 100644 index 64fa0e20..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MU.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MV.svg b/app/shared/src/commonMain/resources/MR/images/MV.svg deleted file mode 100644 index c94a65c5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MV.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MX.svg b/app/shared/src/commonMain/resources/MR/images/MX.svg deleted file mode 100644 index e9203c24..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MX.svg +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/MY.svg b/app/shared/src/commonMain/resources/MR/images/MY.svg deleted file mode 100644 index 5ee8e52f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/MY.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NA.svg b/app/shared/src/commonMain/resources/MR/images/NA.svg deleted file mode 100644 index f9d0516e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NA.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NE.svg b/app/shared/src/commonMain/resources/MR/images/NE.svg deleted file mode 100644 index 0845a14f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NE.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NG.svg b/app/shared/src/commonMain/resources/MR/images/NG.svg deleted file mode 100644 index e5ea783b..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NG.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NI.svg b/app/shared/src/commonMain/resources/MR/images/NI.svg deleted file mode 100644 index 716e4e9c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NI.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NKR.svg b/app/shared/src/commonMain/resources/MR/images/NKR.svg deleted file mode 100644 index 3f2763a2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NKR.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NL.svg b/app/shared/src/commonMain/resources/MR/images/NL.svg deleted file mode 100644 index 01899c21..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NL.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NO.svg b/app/shared/src/commonMain/resources/MR/images/NO.svg deleted file mode 100644 index 2ba1dc27..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NO.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NR.svg b/app/shared/src/commonMain/resources/MR/images/NR.svg deleted file mode 100644 index 90c67301..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NR.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NU.svg b/app/shared/src/commonMain/resources/MR/images/NU.svg deleted file mode 100644 index 26f7c762..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NU.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/NZ.svg b/app/shared/src/commonMain/resources/MR/images/NZ.svg deleted file mode 100644 index b56cf9b3..00000000 --- a/app/shared/src/commonMain/resources/MR/images/NZ.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PA.svg b/app/shared/src/commonMain/resources/MR/images/PA.svg deleted file mode 100644 index d2b95955..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PA.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PE.svg b/app/shared/src/commonMain/resources/MR/images/PE.svg deleted file mode 100644 index 3fba1d21..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PE.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PG.svg b/app/shared/src/commonMain/resources/MR/images/PG.svg deleted file mode 100644 index be0b77fa..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PG.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PH.svg b/app/shared/src/commonMain/resources/MR/images/PH.svg deleted file mode 100644 index 185bfeff..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PH.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PK.svg b/app/shared/src/commonMain/resources/MR/images/PK.svg deleted file mode 100644 index a269eb4e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PK.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PL.svg b/app/shared/src/commonMain/resources/MR/images/PL.svg deleted file mode 100644 index ee18c429..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PL.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PMR.svg b/app/shared/src/commonMain/resources/MR/images/PMR.svg deleted file mode 100644 index 28173248..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PMR.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PS.svg b/app/shared/src/commonMain/resources/MR/images/PS.svg deleted file mode 100644 index 2a3b0cd6..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PS.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PT.svg b/app/shared/src/commonMain/resources/MR/images/PT.svg deleted file mode 100644 index 81609b41..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PT.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PW.svg b/app/shared/src/commonMain/resources/MR/images/PW.svg deleted file mode 100644 index 8ee106ea..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PW.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/PayPal.svg b/app/shared/src/commonMain/resources/MR/images/PayPal.svg deleted file mode 100644 index b214e9f2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PayPal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/PulZ.svg b/app/shared/src/commonMain/resources/MR/images/PulZ.svg deleted file mode 100644 index 37ec26cb..00000000 --- a/app/shared/src/commonMain/resources/MR/images/PulZ.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/QA.svg b/app/shared/src/commonMain/resources/MR/images/QA.svg deleted file mode 100644 index b387d9fd..00000000 --- a/app/shared/src/commonMain/resources/MR/images/QA.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/RO.svg b/app/shared/src/commonMain/resources/MR/images/RO.svg deleted file mode 100644 index 4ff8539f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/RO.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/RS.svg b/app/shared/src/commonMain/resources/MR/images/RS.svg deleted file mode 100644 index c891fa60..00000000 --- a/app/shared/src/commonMain/resources/MR/images/RS.svg +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/RU.svg b/app/shared/src/commonMain/resources/MR/images/RU.svg deleted file mode 100644 index 4e74b58d..00000000 --- a/app/shared/src/commonMain/resources/MR/images/RU.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SE.svg b/app/shared/src/commonMain/resources/MR/images/SE.svg deleted file mode 100644 index 4ffd9032..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SE.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SG.svg b/app/shared/src/commonMain/resources/MR/images/SG.svg deleted file mode 100644 index de68a4e2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SG.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SH.svg b/app/shared/src/commonMain/resources/MR/images/SH.svg deleted file mode 100644 index d331b412..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SH.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SI.svg b/app/shared/src/commonMain/resources/MR/images/SI.svg deleted file mode 100644 index ec2e70f3..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SI.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SK.svg b/app/shared/src/commonMain/resources/MR/images/SK.svg deleted file mode 100644 index cd6fc39e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SK.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SO.svg b/app/shared/src/commonMain/resources/MR/images/SO.svg deleted file mode 100644 index d9310572..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SO.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/ST.svg b/app/shared/src/commonMain/resources/MR/images/ST.svg deleted file mode 100644 index 73f7cc39..00000000 --- a/app/shared/src/commonMain/resources/MR/images/ST.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/SY.svg b/app/shared/src/commonMain/resources/MR/images/SY.svg deleted file mode 100644 index 23cbda1f..00000000 --- a/app/shared/src/commonMain/resources/MR/images/SY.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/TH.svg b/app/shared/src/commonMain/resources/MR/images/TH.svg deleted file mode 100644 index 4dc53549..00000000 --- a/app/shared/src/commonMain/resources/MR/images/TH.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/TL.svg b/app/shared/src/commonMain/resources/MR/images/TL.svg deleted file mode 100644 index df0d8f77..00000000 --- a/app/shared/src/commonMain/resources/MR/images/TL.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/TO.svg b/app/shared/src/commonMain/resources/MR/images/TO.svg deleted file mode 100644 index 4a4ac352..00000000 --- a/app/shared/src/commonMain/resources/MR/images/TO.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/TR.svg b/app/shared/src/commonMain/resources/MR/images/TR.svg deleted file mode 100644 index 25fa8e93..00000000 --- a/app/shared/src/commonMain/resources/MR/images/TR.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/TW.svg b/app/shared/src/commonMain/resources/MR/images/TW.svg deleted file mode 100644 index 2ab2d514..00000000 --- a/app/shared/src/commonMain/resources/MR/images/TW.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/Twitter.svg b/app/shared/src/commonMain/resources/MR/images/Twitter.svg deleted file mode 100644 index d60af2b8..00000000 --- a/app/shared/src/commonMain/resources/MR/images/Twitter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/UA.svg b/app/shared/src/commonMain/resources/MR/images/UA.svg deleted file mode 100644 index 950351b7..00000000 --- a/app/shared/src/commonMain/resources/MR/images/UA.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/UK.svg b/app/shared/src/commonMain/resources/MR/images/UK.svg deleted file mode 100644 index b1a86d19..00000000 --- a/app/shared/src/commonMain/resources/MR/images/UK.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/UY.svg b/app/shared/src/commonMain/resources/MR/images/UY.svg deleted file mode 100644 index 9b1f74eb..00000000 --- a/app/shared/src/commonMain/resources/MR/images/UY.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/UZ.svg b/app/shared/src/commonMain/resources/MR/images/UZ.svg deleted file mode 100644 index e1e56bce..00000000 --- a/app/shared/src/commonMain/resources/MR/images/UZ.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/VA.svg b/app/shared/src/commonMain/resources/MR/images/VA.svg deleted file mode 100644 index c13e762c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/VA.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/VE.svg b/app/shared/src/commonMain/resources/MR/images/VE.svg deleted file mode 100644 index 34deff00..00000000 --- a/app/shared/src/commonMain/resources/MR/images/VE.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/VN.svg b/app/shared/src/commonMain/resources/MR/images/VN.svg deleted file mode 100644 index 53d8b8ed..00000000 --- a/app/shared/src/commonMain/resources/MR/images/VN.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/WS.svg b/app/shared/src/commonMain/resources/MR/images/WS.svg deleted file mode 100644 index 9b5e56a4..00000000 --- a/app/shared/src/commonMain/resources/MR/images/WS.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/XK.svg b/app/shared/src/commonMain/resources/MR/images/XK.svg deleted file mode 100644 index 11209181..00000000 --- a/app/shared/src/commonMain/resources/MR/images/XK.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/YE.svg b/app/shared/src/commonMain/resources/MR/images/YE.svg deleted file mode 100644 index 5493eec2..00000000 --- a/app/shared/src/commonMain/resources/MR/images/YE.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/shared/src/commonMain/resources/MR/images/browser_dark.svg b/app/shared/src/commonMain/resources/MR/images/browser_dark.svg deleted file mode 100644 index 9c6a5ff0..00000000 --- a/app/shared/src/commonMain/resources/MR/images/browser_dark.svg +++ /dev/null @@ -1,2 +0,0 @@ - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/browser_light.svg b/app/shared/src/commonMain/resources/MR/images/browser_light.svg deleted file mode 100644 index 6e57a76c..00000000 --- a/app/shared/src/commonMain/resources/MR/images/browser_light.svg +++ /dev/null @@ -1,2 +0,0 @@ - \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/movie_dark.svg b/app/shared/src/commonMain/resources/MR/images/movie_dark.svg deleted file mode 100644 index d6452e09..00000000 --- a/app/shared/src/commonMain/resources/MR/images/movie_dark.svg +++ /dev/null @@ -1 +0,0 @@ -horror movie \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/movie_light.svg b/app/shared/src/commonMain/resources/MR/images/movie_light.svg deleted file mode 100644 index 45d4a4b5..00000000 --- a/app/shared/src/commonMain/resources/MR/images/movie_light.svg +++ /dev/null @@ -1 +0,0 @@ -horror movie \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/sad_dark.svg b/app/shared/src/commonMain/resources/MR/images/sad_dark.svg deleted file mode 100644 index e382fbea..00000000 --- a/app/shared/src/commonMain/resources/MR/images/sad_dark.svg +++ /dev/null @@ -1,2 +0,0 @@ -feeling blue \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/sad_light.svg b/app/shared/src/commonMain/resources/MR/images/sad_light.svg deleted file mode 100644 index 87b68250..00000000 --- a/app/shared/src/commonMain/resources/MR/images/sad_light.svg +++ /dev/null @@ -1,2 +0,0 @@ -feeling blue \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/server_dark.svg b/app/shared/src/commonMain/resources/MR/images/server_dark.svg deleted file mode 100644 index 1ecf1c10..00000000 --- a/app/shared/src/commonMain/resources/MR/images/server_dark.svg +++ /dev/null @@ -1 +0,0 @@ -server_cluster \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/server_light.svg b/app/shared/src/commonMain/resources/MR/images/server_light.svg deleted file mode 100644 index 01e2350e..00000000 --- a/app/shared/src/commonMain/resources/MR/images/server_light.svg +++ /dev/null @@ -1 +0,0 @@ -server_cluster \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/App.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/App.desktop.kt deleted file mode 100644 index e6a7dff6..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/App.desktop.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.datlag.burningseries.shared - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.LocalContextMenuRepresentation -import androidx.compose.foundation.text.LocalTextContextMenu -import androidx.compose.material3.ColorScheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.awt.ComposeWindow -import com.dzirbel.contextmenu.ContextMenuColors -import com.dzirbel.contextmenu.MaterialContextMenuRepresentation -import com.dzirbel.contextmenu.MaterialTextContextMenu - -val LocalWindow = compositionLocalOf { error("No window state provided") } - -@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class) -@Composable -actual fun SystemProvider(content: @Composable () -> Unit) { - CompositionLocalProvider( - LocalContextMenuRepresentation provides MaterialContextMenuRepresentation(colors = ContextMenuColors(MaterialTheme.colorScheme)), - LocalTextContextMenu provides MaterialTextContextMenu, - ) { - content() - } -} - -@Composable -fun ContextMenuColors(scheme: ColorScheme = MaterialTheme.colorScheme) = ContextMenuColors( - surface = scheme.surface, - text = scheme.onSurface, - icon = scheme.onSurface, - divider = scheme.onSurfaceVariant, - shortcutText = scheme.onSurfaceVariant -) - -@Composable -actual fun rememberIsTv(): Boolean { - return false -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/AppIO.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/AppIO.kt deleted file mode 100644 index 4579f08c..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/AppIO.kt +++ /dev/null @@ -1,150 +0,0 @@ -package dev.datlag.burningseries.shared - -import androidx.compose.ui.awt.ComposeWindow -import androidx.compose.ui.res.useResource -import dev.datlag.burningseries.model.common.* -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.common.withMainContext -import dev.icerock.moko.resources.AssetResource -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import net.harawata.appdirs.AppDirsFactory -import org.apache.commons.lang3.SystemUtils -import java.awt.Image -import java.awt.Toolkit -import java.io.File -import java.io.InputStream -import javax.swing.ImageIcon - -object AppIO { - - private val dirs by lazy { - AppDirsFactory.getInstance() - } - - fun applyTitle(title: String) = scopeCatching { - val toolkit = Toolkit.getDefaultToolkit() - val awtAppClassNameField = toolkit.javaClass.getDeclaredField("awtAppClassName") - val working = try { - awtAppClassNameField.isAccessible = true - awtAppClassNameField.canAccess(null) - } catch (ignored: Throwable) { - awtAppClassNameField.trySetAccessible() - } - awtAppClassNameField.set(toolkit, title) - working - }.getOrNull() ?: false - - fun loadAppIcon( - window: ComposeWindow, - scope: CoroutineScope, - vararg assets: AssetResource - ) = scope.launchIO { - val appIcons = assets.map { async { - getAppImage(it) - } }.awaitAll().filterNotNull() - - withMainContext { - window.iconImages = appIcons - } - } - - private suspend fun getAppImage(asset: AssetResource): Image? = suspendCatching { - (getResourceAsInputStream(asset.filePath) - ?: getResourceAsInputStream(asset.originalPath) - ?: asset.resourcesClassLoader.getResourceAsStream(asset.filePath) - )?.use { - ImageIcon(it.readBytes()).image - } - }.getOrNull() - - private fun getResourceAsInputStream(location: String): InputStream? { - val classLoader = AppIO::class.java.classLoader ?: this::class.java.classLoader - return scopeCatching { - classLoader?.getResourceAsStream(location) - }.getOrNull() ?: scopeCatching { - AppIO::class.java.getResourceAsStream(location) - }.getOrNull() ?: scopeCatching { - this::class.java.getResourceAsStream(location) - }.getOrNull() ?: scopeCatching { - useResource(location) { it } - }.getOrNull() - } - - fun getFileInConfigDir(name: String): File { - val parentFile = File(dirs.getUserConfigDir(APP_NAME, null, null)) - var returnFile = File(parentFile, name) - if (returnFile.existsRWSafely() - || (returnFile.parentFile ?: parentFile).existsRWSafely() - || (returnFile.parentFile ?: parentFile).mkdirsSafely()) { - return returnFile - } else if (SystemUtils.IS_OS_LINUX) { - val configDir = File(homeDirectory(), ".config/$APP_NAME").apply { - mkdirsSafely() - } - returnFile = File(configDir, name) - return returnFile - } - return returnFile - } - - fun getFileInUserDataDir(name: String): File { - val parentFile = File(dirs.getUserDataDir(APP_NAME, null, null)) - var returnFile = File(parentFile, name) - - if (returnFile.existsRWSafely() - || (returnFile.parentFile ?: parentFile).existsRWSafely() - || (returnFile.parentFile ?: parentFile).mkdirsSafely()) { - return returnFile - } else if (SystemUtils.IS_OS_LINUX) { - val dataDir = File(homeDirectory(), ".local/share/$APP_NAME").apply { - mkdirsSafely() - } - returnFile = File(dataDir, name) - return returnFile - } - return returnFile - } - - fun getFileInSiteDataDir(name: String): File { - val parentFile = File(dirs.getSiteDataDir(APP_NAME, null, null)) - var returnFile = File(parentFile, name) - if (returnFile.existsRWSafely() - || (returnFile.parentFile ?: parentFile).existsRWSafely() - || (returnFile.parentFile ?: parentFile).mkdirsSafely()) { - return returnFile - } else if (SystemUtils.IS_OS_LINUX) { - val dataDir = File("/usr/local/share/$APP_NAME") - returnFile = File(dataDir, name) - if (returnFile.existsRWSafely() - || (returnFile.parentFile ?: dataDir).existsRWSafely() - || (returnFile.parentFile ?: dataDir).mkdirsSafely()) { - return returnFile - } - - val alternativeDataDir = File(homeDirectory(), ".local/share/flatpak/exports/share/$APP_NAME").apply { - mkdirsSafely() - } - returnFile = File(alternativeDataDir, name) - return returnFile - } - return returnFile - } - - fun getWriteableExecutableFolder(): File { - val resDir = systemProperty("compose.application.resources.dir")?.let { File(it) } - return if (resDir.existsRWSafely()) { - resDir!! - } else { - if (File("./").canWriteSafely()) { - File("./") - } else { - getFileInSiteDataDir("./") - } - } - } - - private const val APP_NAME = "Burning-Series" - internal const val VLC_ORG = "https://www.videolan.org/vlc" -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/PackageName.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/PackageName.desktop.kt deleted file mode 100644 index 34ef274d..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/PackageName.desktop.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.datlag.burningseries.shared - -@Suppress("NewApi") -actual fun getPackageName(): String { - val clazz = AppIO::class - - return (clazz.java.packageName.ifBlank { null } ?: run { - var cutPackage = (clazz.qualifiedName ?: clazz.java.canonicalName).substringBeforeLast(clazz.simpleName ?: clazz.java.simpleName) - - if (cutPackage.startsWith('.')) { - cutPackage = cutPackage.substring(1) - } - if (cutPackage.endsWith('.')) { - cutPackage = cutPackage.substringBeforeLast('.') - } - - cutPackage - }).trim() -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.desktop.kt deleted file mode 100644 index bc817bc1..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCompose.desktop.kt +++ /dev/null @@ -1,56 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.toComposeImageBitmap -import com.arkivanov.decompose.ExperimentalDecomposeApi -import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation -import com.arkivanov.decompose.extensions.compose.stack.animation.fade -import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation -import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation -import com.arkivanov.essenty.backhandler.BackHandler -import com.vanniktech.blurhash.BlurHash -import org.kodein.di.DI -import androidx.compose.foundation.onClick as defaultClick -import com.vanniktech.blurhash.BlurHash.decode as defaultDecode - -@OptIn(ExperimentalFoundationApi::class) -actual fun Modifier.onClick( - enabled: Boolean, - onDoubleClick: (() -> Unit)?, - onLongClick: (() -> Unit)?, - onClick: () -> Unit -): Modifier { - return this.defaultClick( - enabled = enabled, - onDoubleClick = onDoubleClick, - onLongClick = onLongClick, - onClick = onClick - ) -} - -@OptIn(ExperimentalDecomposeApi::class) -actual fun backAnimation( - backHandler: BackHandler, - onBack: () -> Unit -): StackAnimation = predictiveBackAnimation( - backHandler = backHandler, - fallbackAnimation = stackAnimation(fade()), - onBack = onBack -) - -val LocalRestartRequired = compositionLocalOf { false } -actual fun BlurHash.decode( - hash: String, - width: Int, - height: Int -): ImageBitmap? { - val image = defaultDecode(hash, width, height) - return image?.toComposeImageBitmap() -} - -actual fun String.openInBrowser(di: DI) { - this.openInBrowser() -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.desktop.kt deleted file mode 100644 index 0318c58e..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendCoroutine.desktop.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainCoroutineDispatcher - -actual val Dispatchers.DeviceMain: MainCoroutineDispatcher - get() = Main - -actual val Dispatchers.DeviceIO: CoroutineDispatcher - get() = IO - -actual val Dispatchers.DeviceDefault : CoroutineDispatcher - get() = Default \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt deleted file mode 100644 index 0a98619e..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/common/PlatformExtendString.kt +++ /dev/null @@ -1,48 +0,0 @@ -package dev.datlag.burningseries.shared.common - -import dev.datlag.burningseries.model.common.scopeCatching -import java.awt.Desktop -import java.net.URI - -internal fun String.openInBrowser() = scopeCatching { - val openBrowserCommands = arrayOf( - arrayOf("xdg-open", "$1"), - arrayOf("gio", "open", "$1"), - arrayOf("gvfs-open", "$1"), - arrayOf("gnome-open", "$1"), - arrayOf("mate-open", "$1"), - arrayOf("exo-open", "$1"), - arrayOf("enlightenment_open", "$1"), - arrayOf( - "gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", - "--object-path", "/org/freedesktop/portal/desktop", - "--method", "org.freedesktop.portal.OpenURI.OpenURI", - "", "$1", "{}" - ), - arrayOf("open", "$1"), - arrayOf("rundll32", "url.dll,FileProtocolHandler", "$1") - ) - - openBrowserCommands.forEach { browser -> - try { - val command = arrayOfNulls(browser.size) - for (i in browser.indices) { - if (browser[i] == "$1") { - command[i] = this - } else { - command[i] = browser[i] - } - } - if (Runtime.getRuntime().exec(command).waitFor() == 0) { - return@scopeCatching - } - } catch (ignored: Throwable) { - } - } - - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(URI.create(this)) - } else { - throw IllegalStateException() - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.desktop.kt deleted file mode 100644 index 9923bdd4..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/module/PlatformModule.desktop.kt +++ /dev/null @@ -1,146 +0,0 @@ -package dev.datlag.burningseries.shared.module - -import android.app.Application -import coil3.ImageLoader -import coil3.PlatformContext -import coil3.annotation.ExperimentalCoilApi -import coil3.disk.DiskCache -import coil3.memory.MemoryCache -import coil3.network.ktor.KtorNetworkFetcherFactory -import coil3.request.crossfade -import dev.datlag.burningseries.database.DriverFactory -import dev.datlag.burningseries.model.common.canWriteSafely -import dev.datlag.burningseries.shared.AppIO -import dev.datlag.burningseries.shared.Sekret -import dev.datlag.burningseries.shared.getPackageName -import dev.datlag.burningseries.shared.other.StateSaver -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseOptions -import dev.gitlive.firebase.firestore.firestore -import dev.gitlive.firebase.initialize -import io.ktor.client.* -import io.ktor.client.engine.okhttp.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.realm.kotlin.mongodb.AppConfiguration -import kotlinx.serialization.json.Json -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient -import okhttp3.dnsoverhttps.DnsOverHttps -import okio.FileSystem -import org.kodein.di.DI -import org.kodein.di.bindEagerSingleton -import org.kodein.di.bindSingleton -import org.kodein.di.instance -import java.net.InetAddress -import java.util.concurrent.TimeUnit - -actual object PlatformModule { - - private const val NAME = "PlatformModuleDesktop" - - @OptIn(ExperimentalCoilApi::class) - actual val di: DI.Module = DI.Module(NAME) { - bindSingleton { - Json { - ignoreUnknownKeys = true - isLenient = true - } - } - bindSingleton { - OkHttpClient.Builder() - .followRedirects(true) - .followSslRedirects(true) - .connectTimeout(3, TimeUnit.MINUTES) - .readTimeout(3, TimeUnit.MINUTES) - .writeTimeout(3, TimeUnit.MINUTES) - .build() - } - bindSingleton { - DnsOverHttps.Builder() - .client(instance()) - .url("https://dns.google/dns-query".toHttpUrl()) - .bootstrapDnsHosts(InetAddress.getByName("8.8.4.4"), InetAddress.getByName("8.8.8.8")) - .build() - } - bindSingleton { - HttpClient(OkHttp) { - engine { - config { - followRedirects(true) - connectTimeout(3, TimeUnit.MINUTES) - readTimeout(3, TimeUnit.MINUTES) - writeTimeout(3, TimeUnit.MINUTES) - dns(instance()) - } - } - install(ContentNegotiation) { - json(instance(), ContentType.Application.Json) - json(instance(), ContentType.Text.Plain) - } - } - } - bindSingleton("BurningSeriesDBFile") { - AppIO.getFileInUserDataDir("bs.db") - } - bindSingleton { - DriverFactory(instance("BurningSeriesDBFile")) - } - if (StateSaver.sekretLibraryLoaded) { - bindEagerSingleton { - AppIO.getWriteableExecutableFolder().let { - if (it.canWriteSafely()) { - AppConfiguration.Builder(Sekret().mongoApplication(getPackageName())!!) - .syncRootDirectory(it.canonicalPath) - .build() - } else { - AppConfiguration.create(Sekret().mongoApplication(getPackageName())!!) - } - } - - } - bindEagerSingleton { - Firebase.initialize( - context = Application(), - options = FirebaseOptions( - applicationId = Sekret().firebaseApplication(getPackageName())!!, - apiKey = Sekret().firebaseApiKey(getPackageName())!!, - projectId = Sekret().firebaseProject(getPackageName()) - ) - ) - } - bindEagerSingleton { - val store = Firebase.firestore(instance()) - - store.setSettings( - persistenceEnabled = false - ) - store - } - } - bindSingleton { - PlatformContext.INSTANCE - } - bindSingleton { - ImageLoader.Builder(instance()) - .components { - add(KtorNetworkFetcherFactory(instance())) - } - .memoryCache { - MemoryCache.Builder() - .maxSizePercent(instance()) - .build() - } - .diskCache { - DiskCache.Builder() - .directory(FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "image_cache") - .maxSizeBytes(512L * 1024 * 1024) // 512MB - .build() - } - .crossfade(true) - .build() - } - } - -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/CEFState.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/CEFState.kt deleted file mode 100644 index 318362a5..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/CEFState.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.datlag.burningseries.shared.other - -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.staticCompositionLocalOf - -val LocalCEFInitialization = staticCompositionLocalOf> { error("No CEFInitialization state provided") } - -sealed interface CEFState { - data object LOCATING : CEFState - data class Downloading(val progress: Float) : CEFState - data object EXTRACTING : CEFState - data object INSTALLING : CEFState - data object INITIALIZING : CEFState - data object INITIALIZED : CEFState -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.desktop.kt deleted file mode 100644 index a44a041d..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/other/Crashlytics.desktop.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.datlag.burningseries.shared.other - -import dev.datlag.burningseries.model.common.name -import dev.datlag.burningseries.shared.ui.navigation.Component - -actual object Crashlytics { - actual fun customKey(key: String, value: String) { } - actual fun customKey(key: String, value: Boolean) { } - actual fun customKey(key: String, value: Int) { } - actual fun customKey(key: String, value: Long) { } - actual fun customKey(key: String, value: Float) { } - actual fun customKey(key: String, value: Double) { } - actual fun screen(value: Component) { - customKey("Screen", value::class.name) - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.desktop.kt deleted file mode 100644 index 9fa46146..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ProjectCard.desktop.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.openInBrowser -import dev.datlag.burningseries.shared.other.Project -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun ProjectCard(project: Project, modifier: Modifier) { - ElevatedCard( - modifier = modifier, - onClick = { - (project.github ?: project.googlePlay)?.openInBrowser() - } - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - project.icon?.let { - Image( - modifier = Modifier.size(64.dp), - painter = painterResource(it), - contentDescription = stringResource(project.title) - ) - } - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(project.title), - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.titleLarge - ) - Text(text = stringResource(project.subTitle)) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.desktop.kt deleted file mode 100644 index 8c3faa0a..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.desktop.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom - -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier - -@Composable -actual fun VerticalScrollbar(adapter: ScrollbarAdapter, modifier: Modifier) { - androidx.compose.foundation.VerticalScrollbar( - adapter = adapter, - modifier = modifier - ) -} - -@Composable -actual fun rememberScrollbarAdapter( - scrollState: LazyGridState, -): ScrollbarAdapter = remember(scrollState) { - androidx.compose.foundation.ScrollbarAdapter(scrollState) -} - -@Composable -actual fun rememberScrollbarAdapter( - scrollState: LazyListState -): ScrollbarAdapter = remember(scrollState) { - androidx.compose.foundation.ScrollbarAdapter(scrollState) -} - -actual typealias ScrollbarAdapter = androidx.compose.foundation.v2.ScrollbarAdapter \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.desktop.kt deleted file mode 100644 index 4586c9e6..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/home/component/DeviceContent.desktop.kt +++ /dev/null @@ -1,141 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.home.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.RestartAlt -import androidx.compose.material3.* -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.model.Release -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.LocalRestartRequired -import dev.datlag.burningseries.shared.common.header -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.openInBrowser -import dev.datlag.burningseries.shared.other.CEFState -import dev.datlag.burningseries.shared.other.LocalCEFInitialization -import dev.datlag.burningseries.shared.window.ApplicationDisposer -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.StateFlow - -actual fun LazyGridScope.DeviceContent(release: StateFlow, onDeviceReachable: StateFlow) { - header { - val newRelease by release.collectAsStateWithLifecycle() - - if (newRelease != null) { - Row( - modifier = Modifier.padding(top = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Column( - modifier = Modifier.weight(1F), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = stringResource(SharedRes.strings.release_available_title), - style = MaterialTheme.typography.headlineMedium - ) - Text( - text = stringResource(SharedRes.strings.release_available_text, newRelease!!.title) - ) - } - Button( - onClick = { - newRelease!!.htmlUrl.openInBrowser() - } - ) { - Image( - modifier = Modifier.size(ButtonDefaults.IconSize), - painter = painterResource(SharedRes.images.GitHub), - contentDescription = stringResource(SharedRes.strings.github), - colorFilter = ColorFilter.tint(LocalContentColor.current) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.github)) - } - } - } else { - val cefInitState by LocalCEFInitialization.current - val restartRequired = LocalRestartRequired.current - - if (restartRequired) { - val disposer = ApplicationDisposer.current - - Row( - modifier = Modifier.padding(top = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Column( - modifier = Modifier.weight(1F), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = stringResource(SharedRes.strings.restart_required_title), - style = MaterialTheme.typography.headlineMedium - ) - Text( - text = stringResource(SharedRes.strings.restart_required_text) - ) - } - Button( - onClick = { - disposer.restart() - } - ) { - Icon( - modifier = Modifier.size(ButtonDefaults.IconSize), - imageVector = Icons.Default.RestartAlt, - contentDescription = stringResource(SharedRes.strings.restart) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.restart)) - } - } - } else { - when (val current = cefInitState) { - is CEFState.Downloading -> { - Column( - modifier = Modifier.padding(top = 16.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = stringResource(SharedRes.strings.downloading), - style = MaterialTheme.typography.headlineMedium - ) - - Text( - text = stringResource(SharedRes.strings.downloading_text) - ) - - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth().clip(CircleShape), - progress = current.progress / 100F - ) - } - } - is CEFState.INITIALIZED -> { - val reachable by onDeviceReachable.collectAsStateWithLifecycle() - - if (!reachable) { - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(SharedRes.strings.enable_custom_dns) - ) - } - } - else -> { } - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.desktop.kt deleted file mode 100644 index 59a6b2fb..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/SeriesScreen.desktop.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series - -import androidx.compose.runtime.Composable - -@Composable -actual fun EnterSeriesScreen() { } \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.desktop.kt deleted file mode 100644 index 68264c4a..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebView.desktop.kt +++ /dev/null @@ -1,56 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.SwingPanel -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.withIOContext -import dev.datlag.burningseries.shared.common.withMainContext -import dev.datlag.burningseries.shared.other.CEFState -import dev.datlag.burningseries.shared.other.LocalCEFInitialization -import dev.datlag.burningseries.shared.ui.custom.state.BrowserState -import dev.datlag.kcef.KCEF -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive - -@Composable -actual fun WebView(url: String, modifier: Modifier, onScraped: (String) -> Unit) { - val cefInitState by LocalCEFInitialization.current - - if (cefInitState is CEFState.INITIALIZED) { - val client = remember { KCEF.newClientBlocking() } - val browser = remember { client.createBrowser(url) } - val scrapingJs = SharedRes.assets.scrape_hoster_cef.readText() - - SwingPanel( - background = MaterialTheme.colorScheme.background, - factory = { - browser.uiComponent - }, - modifier = modifier.padding(8.dp) - ) - - LaunchedEffect(browser) { - withIOContext { - do { - delay(3000) - withMainContext { - browser.evaluateJavaScript(scrapingJs) { - if (it != null) { - onScraped(it) - } - } - } - } while (isActive) - } - } - } else { - BrowserState(SharedRes.strings.browser_initializing) - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.desktop.kt deleted file mode 100644 index 7477de32..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/error/ErrorDialog.desktop.kt +++ /dev/null @@ -1,59 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.error - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun ErrorDialog(component: ErrorComponent) { - Card( - modifier = Modifier.fillMaxWidth().padding(8.dp) - ) { - Text( - text = stringResource(SharedRes.strings.activate_error_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true, - modifier = Modifier.padding(8.dp) - ) - Text( - text = stringResource(SharedRes.strings.activate_error_text), - modifier = Modifier.padding(8.dp) - ) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(8.dp).align(Alignment.End) - ) { - if (component.stream != null) { - Button( - onClick = { - component.watch(component.stream!!) - } - ) { - Text(text = stringResource(SharedRes.strings.watch)) - } - } - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.desktop.kt deleted file mode 100644 index d4099f5e..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/dialog/success/SuccessDialog.desktop.kt +++ /dev/null @@ -1,59 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.dialog.success - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.SharedRes -import dev.icerock.moko.resources.compose.stringResource - -@Composable -actual fun SuccessDialog(component: SuccessComponent) { - Card( - modifier = Modifier.fillMaxWidth().padding(8.dp) - ) { - Text( - text = stringResource(SharedRes.strings.activate_success_title), - style = MaterialTheme.typography.headlineMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - softWrap = true, - modifier = Modifier.padding(8.dp) - ) - Text( - text = stringResource(SharedRes.strings.activate_success_text), - modifier = Modifier.padding(8.dp) - ) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(8.dp).align(Alignment.End) - ) { - if (component.stream != null) { - Button( - onClick = { - component.watch(component.stream!!) - } - ) { - Text(text = stringResource(SharedRes.strings.watch)) - } - } - Button( - onClick = { - component.dismiss() - } - ) { - Text(text = stringResource(SharedRes.strings.close)) - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.desktop.kt deleted file mode 100644 index 918f33fa..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/AniFlowCard.desktop.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.component - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import kotlinx.coroutines.flow.StateFlow - -/** - * Keep single implementation instead of ProjectCard, to support hint for not connected. - */ -@Composable -actual fun AniFlowCard( - isAnime: StateFlow, - modifier: Modifier -) { } \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoControls.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoControls.kt deleted file mode 100644 index c8aec845..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoControls.kt +++ /dev/null @@ -1,178 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -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.WindowPlacement -import dev.datlag.burningseries.shared.LocalWindow -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.toDuration -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun VideoControls( - mediaPlayer: MediaPlayer -) { - Column( - modifier = Modifier.fillMaxWidth().background(Color.Black) - ) { - val time by remember { - derivedStateOf { mediaPlayer.time.value } - } - val length by remember { - derivedStateOf { mediaPlayer.length.value } - } - val window = LocalWindow.current - var originalPlacement = remember(window) { window.placement } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { - mediaPlayer.rewind() - } - ) { - Icon( - imageVector = Icons.Default.Replay10, - contentDescription = stringResource(SharedRes.strings.rewind), - tint = Color.White - ) - } - if (mediaPlayer.isPlaying.value) { - IconButton( - onClick = { - mediaPlayer.pause() - } - ) { - Icon( - imageVector = Icons.Default.Pause, - contentDescription = stringResource(SharedRes.strings.pause), - tint = Color.White - ) - } - } else { - IconButton( - onClick = { - mediaPlayer.play() - } - ) { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = stringResource(SharedRes.strings.play), - tint = Color.White - ) - } - } - IconButton( - onClick = { - mediaPlayer.forward() - } - ) { - Icon( - imageVector = Icons.Default.Forward10, - contentDescription = stringResource(SharedRes.strings.forward), - tint = Color.White - ) - } - Text( - text = time.toDuration(), - textAlign = TextAlign.Center, - color = Color.White - ) - Slider( - modifier = Modifier.weight(1F), - value = time.toDouble().toFloat(), - onValueChange = { - mediaPlayer.seekTo(it.toLong()) - }, - valueRange = 0F..length.toDouble().toFloat(), - colors = SliderDefaults.colors( - thumbColor = MaterialTheme.colorScheme.primary, - activeTrackColor = MaterialTheme.colorScheme.primary, - inactiveTrackColor = Color.White.copy(alpha = 0.2F) - ) - ) - Text( - text = length.toDuration(), - textAlign = TextAlign.Center, - color = Color.White - ) - if (window.placement == WindowPlacement.Fullscreen) { - IconButton( - onClick = { - window.placement = originalPlacement - } - ) { - Icon( - imageVector = Icons.Default.FullscreenExit, - contentDescription = stringResource(SharedRes.strings.fullscreen), - tint = Color.White - ) - } - } else { - IconButton( - onClick = { - originalPlacement = window.placement - window.placement = WindowPlacement.Fullscreen - } - ) { - Icon( - imageVector = Icons.Default.Fullscreen, - contentDescription = stringResource(SharedRes.strings.fullscreen), - tint = Color.White - ) - } - } - - if (mediaPlayer.isMuted.value) { - IconButton( - onClick = { - mediaPlayer.unmute() - } - ) { - Icon( - imageVector = Icons.Default.VolumeOff, - contentDescription = stringResource(SharedRes.strings.muted) - ) - } - } else { - IconButton( - onClick = { - mediaPlayer.mute() - } - ) { - Icon( - imageVector = Icons.Default.VolumeUp, - contentDescription = stringResource(SharedRes.strings.unmuted) - ) - } - } - - Slider( - value = mediaPlayer.volume.value, - onValueChange = { - mediaPlayer.setVolume(it) - }, - valueRange = 0F..100F, - modifier = Modifier.width(100.dp) - ) - } - - DisposableEffect(window) { - onDispose { - window.placement = originalPlacement - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoPlayer.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoPlayer.kt deleted file mode 100644 index f602e4da..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoPlayer.kt +++ /dev/null @@ -1,217 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.SwingPanel -import androidx.compose.ui.graphics.Color -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import org.apache.commons.lang3.SystemUtils -import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery -import uk.co.caprica.vlcj.player.base.MediaPlayer -import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter -import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent -import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent -import uk.co.caprica.vlcj.player.component.MediaPlayerComponent -import kotlin.math.roundToInt - -@Composable -fun VideoPlayer( - component: VideoComponent, - modifier: Modifier -): dev.datlag.burningseries.shared.ui.screen.video.MediaPlayer? { - val foundVlc = NativeDiscovery().discover() - - if (foundVlc) { - val mediaPlayerComponent = remember { - if (SystemUtils.IS_OS_MAC) { - CallbackMediaPlayerComponent() - } else { - EmbeddedMediaPlayerComponent() - } - } - - Box( - modifier = modifier - ) { - SwingPanel( - background = Color.Black, - modifier = Modifier.fillMaxSize(), - factory = { - mediaPlayerComponent - } - ) - } - - val streamList = remember { component.streams } - - var streamIndex by remember(streamList) { mutableIntStateOf(0) } - var sourceIndex by remember(streamIndex) { mutableIntStateOf(0) } - val url = remember(streamIndex, sourceIndex) { streamList[streamIndex].sources.toList()[sourceIndex] } - val headers = remember(streamIndex) { - streamList[streamIndex].headers - } - val startingPos by component.startingPos.collectAsStateWithLifecycle() - - val isPlaying = remember { mutableStateOf(false) } - val length = remember { mutableLongStateOf(0) } - val time = remember { mutableLongStateOf(0) } - val isMuted = remember { mutableStateOf(mediaPlayerComponent.mediaPlayer()?.audio()?.isMute ?: false) } - val volumeState = remember { - val current = mediaPlayerComponent.mediaPlayer()?.audio()?.volume()?.toFloat() ?: 0F - val set = if (current <= 0F) { - if (isMuted.value) { - 0F - } else { - 1F - } - } else { - current - } - - mutableFloatStateOf(set) - } - - val eventListener = remember { object : MediaPlayerEventAdapter() { - override fun error(mediaPlayer: MediaPlayer?) { - super.error(mediaPlayer) - - if (streamList[streamIndex].sources.size - 1 > sourceIndex) { - sourceIndex++ - } else if (streamList.size - 1 > streamIndex) { - streamIndex++ - } - } - - override fun lengthChanged(mediaPlayer: MediaPlayer?, newLength: Long) { - super.lengthChanged(mediaPlayer, newLength) - - component.lengthUpdate(newLength) - length.value = newLength - } - - override fun finished(mediaPlayer: MediaPlayer?) { - super.finished(mediaPlayer) - - component.ended() - } - - override fun timeChanged(mediaPlayer: MediaPlayer?, newTime: Long) { - super.timeChanged(mediaPlayer, newTime) - - component.progressUpdate(newTime) - time.value = newTime - } - - override fun playing(mediaPlayer: MediaPlayer?) { - super.playing(mediaPlayer) - - isPlaying.value = true - } - - override fun paused(mediaPlayer: MediaPlayer?) { - super.paused(mediaPlayer) - - isPlaying.value = false - } - - override fun opening(mediaPlayer: MediaPlayer?) { - super.opening(mediaPlayer) - - (mediaPlayer ?: mediaPlayerComponent.mediaPlayer())?.controls()?.setTime(startingPos) - } - - override fun muted(mediaPlayer: MediaPlayer?, muted: Boolean) { - super.muted(mediaPlayer, muted) - - isMuted.value = muted - } - - override fun volumeChanged(mediaPlayer: MediaPlayer?, volume: Float) { - super.volumeChanged(mediaPlayer, volume) - - volumeState.value = volume * 100 - } - } } - - LaunchedEffect(mediaPlayerComponent, eventListener) { - mediaPlayerComponent.mediaPlayer()?.events()?.addMediaPlayerEventListener(eventListener) - } - - SideEffect { - applyHeaders(headers, mediaPlayerComponent.mediaPlayer()) - mediaPlayerComponent.mediaPlayer()?.media()?.play(url) - } - - DisposableEffect(mediaPlayerComponent) { - onDispose { - mediaPlayerComponent.mediaPlayer()?.release() - } - } - - return remember(mediaPlayerComponent) { object : dev.datlag.burningseries.shared.ui.screen.video.MediaPlayer { - override val isPlaying: MutableState = isPlaying - override val length: MutableLongState = length - override val time: MutableLongState = time - override val isMuted: MutableState = isMuted - override val volume: MutableFloatState = volumeState - - override fun play() { - mediaPlayerComponent.mediaPlayer()?.controls()?.play() - } - - override fun pause() { - mediaPlayerComponent.mediaPlayer()?.controls()?.pause() - } - - override fun rewind() { - mediaPlayerComponent.mediaPlayer()?.controls()?.skipTime(-10000) - } - - override fun forward() { - mediaPlayerComponent.mediaPlayer()?.controls()?.skipTime(10000) - } - - override fun seekTo(millis: Long) { - mediaPlayerComponent.mediaPlayer()?.controls()?.setTime(millis) - } - - override fun mute() { - mediaPlayerComponent.mediaPlayer()?.audio()?.isMute = true - } - - override fun unmute() { - mediaPlayerComponent.mediaPlayer()?.audio()?.isMute = false - } - - override fun setVolume(volume: Float) { - mediaPlayerComponent.mediaPlayer()?.audio()?.setVolume(volume.roundToInt()) - } - } } - } - return null -} - -private fun MediaPlayerComponent.mediaPlayer(): MediaPlayer? { - return when (this) { - is CallbackMediaPlayerComponent -> mediaPlayer() - is EmbeddedMediaPlayerComponent -> mediaPlayer() - else -> null - } -} - -private fun applyHeaders(headers: Map, mediaPlayer: MediaPlayer?) { - if (headers.containsKey("Referer")) { - val referer = headers.getOrElse("Referer") { - headers.entries.firstNotNullOf { - if (it.key.equals("Referer", true)) { - it.value - } else { - null - } - } - } - mediaPlayer?.media()?.options()?.add("--http-referrer", referer) - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.desktop.kt deleted file mode 100644 index d1d80be1..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.desktop.kt +++ /dev/null @@ -1,106 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBackIosNew -import androidx.compose.material.icons.filled.Download -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -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.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.AppIO -import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.openInBrowser -import dev.icerock.moko.resources.compose.stringResource -import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery - -@Composable -actual fun VideoScreen(component: VideoComponent) { - val foundVlc = NativeDiscovery().discover() - - if (foundVlc) { - Column( - modifier = Modifier.fillMaxSize().background(Color.Black) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - val episode by component.episode.collectAsStateWithLifecycle() - - IconButton( - onClick = { - component.back() - } - ) { - Icon( - imageVector = Icons.Default.ArrowBackIosNew, - contentDescription = stringResource(SharedRes.strings.back), - tint = Color.White - ) - } - Text( - text = episode.episodeTitle, - maxLines = 1, - softWrap = true, - overflow = TextOverflow.Ellipsis - ) - } - - val mediaPlayer = VideoPlayer( - component = component, - modifier = Modifier.fillMaxWidth().weight(1F) - ) - - if (mediaPlayer != null) { - VideoControls( - mediaPlayer = mediaPlayer - ) - } - } - } else { - Column( - modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background), - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(SharedRes.strings.vlc_required), - textAlign = TextAlign.Center - ) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = { - component.back() - } - ) { - Text(text = stringResource(SharedRes.strings.back)) - } - Button( - onClick = { - AppIO.VLC_ORG.openInBrowser() - } - ) { - Icon( - modifier = Modifier.size(ButtonDefaults.IconSize), - imageVector = Icons.Default.Download, - contentDescription = stringResource(SharedRes.strings.download) - ) - Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = stringResource(SharedRes.strings.download)) - } - } - } - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.desktop.kt deleted file mode 100644 index d55c2d48..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/dialog/cast/CastDialog.desktop.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.datlag.burningseries.shared.ui.screen.video.dialog.cast - -import androidx.compose.runtime.Composable - -@Composable -actual fun CastDialog(component: CastComponent) { -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.desktop.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.desktop.kt deleted file mode 100644 index 0468c316..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.desktop.kt +++ /dev/null @@ -1,22 +0,0 @@ -package dev.datlag.burningseries.shared.ui.theme - -import androidx.compose.foundation.LocalContextMenuRepresentation -import androidx.compose.material3.ColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.ExperimentalComposeUiApi -import com.dzirbel.contextmenu.MaterialContextMenuRepresentation -import dev.datlag.burningseries.shared.ContextMenuColors - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -actual fun SchemeThemeSystemProvider( - scheme: ColorScheme, - content: @Composable () -> Unit -) { - CompositionLocalProvider( - LocalContextMenuRepresentation provides MaterialContextMenuRepresentation(colors = ContextMenuColors(scheme)) - ) { - content() - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/ApplicationDisposer.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/ApplicationDisposer.kt deleted file mode 100644 index 7eec7044..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/ApplicationDisposer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.datlag.burningseries.shared.window - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.staticCompositionLocalOf - -val LocalApplicationDisposer = staticCompositionLocalOf { - error("No state set for ApplicationDisposer") -} - -interface ApplicationDisposer { - - fun exit() - - fun restart() - - companion object { - val current: ApplicationDisposer - @Composable - @ReadOnlyComposable - get() = LocalApplicationDisposer.current - } -} \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/disposableApplication.kt b/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/disposableApplication.kt deleted file mode 100644 index e05542ba..00000000 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/window/disposableApplication.kt +++ /dev/null @@ -1,104 +0,0 @@ -package dev.datlag.burningseries.shared.window - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.input.key.KeyEvent -import androidx.compose.ui.window.* -import kotlin.system.exitProcess - -fun disposableApplication( - exitProcessOnExit: Boolean = true, - onExitProcess: (() -> Unit)? = null, - content: @Composable ApplicationScope.() -> Unit -) { - var restartApplication = true - - while (restartApplication) { - restartApplication = newApplication(content) - } - - if (exitProcessOnExit) { - onExitProcess?.invoke() - exitProcess(0) - } -} - -fun disposableSingleWindowApplication( - state: WindowState = WindowState(), - visible: Boolean = true, - title: String = "Untitled", - icon: Painter? = null, - undecorated: Boolean = false, - transparent: Boolean = false, - resizable: Boolean = true, - enabled: Boolean = true, - focusable: Boolean = true, - alwaysOnTop: Boolean = false, - onPreviewKeyEvent: (KeyEvent) -> Boolean = { false }, - onKeyEvent: (KeyEvent) -> Boolean = { false }, - exitProcessOnExit: Boolean = true, - onExitProcess: () -> Unit = { }, - content: @Composable FrameWindowScope.() -> Unit -) = disposableApplication(exitProcessOnExit, onExitProcess) { - Window( - onCloseRequest = this::exitApplication, - state = state, - visible = visible, - title = title, - icon = icon, - undecorated = undecorated, - transparent = transparent, - resizable = resizable, - enabled = enabled, - focusable = focusable, - alwaysOnTop = alwaysOnTop, - onPreviewKeyEvent = onPreviewKeyEvent, - onKeyEvent = onKeyEvent, - content = content - ) -} - -private fun newApplication(content: @Composable ApplicationScope.() -> Unit): Boolean { - var shouldRestart = false - - application(exitProcessOnExit = false) { - val applicationDisposer = remember(this) { - ApplicationDisposerImpl { restart -> - shouldRestart = restart - exitApplication() - } - } - - CompositionLocalProvider(LocalApplicationDisposer provides applicationDisposer) { - val disposer = LocalApplicationDisposer.current - - val disposerApplicationScope = remember(disposer) { - object : ApplicationScope { - override fun exitApplication() = disposer.exit() - } - } - - disposerApplicationScope.content() - } - } - - return shouldRestart -} - -private class ApplicationDisposerImpl(private val onExit: (restart: Boolean) -> Unit) : ApplicationDisposer { - - @Volatile - private var isAlive: Boolean = true - - override fun exit() = exitApplication(false) - - override fun restart() = exitApplication(true) - - private fun exitApplication(restart: Boolean) { - check(isAlive) { "Application is no longer alive." } - isAlive = false - onExit(restart) - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e8891cfe..36e828ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,28 +1,28 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.nio.file.Files plugins { + alias(libs.plugins.aboutlibraries) apply false alias(libs.plugins.android) apply false alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false + alias(libs.plugins.apollo) apply false alias(libs.plugins.cocoapods) apply false alias(libs.plugins.compose) apply false - alias(libs.plugins.ksp) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.crashlytics) apply false + alias(libs.plugins.konfig) apply false alias(libs.plugins.ktorfit) apply false + alias(libs.plugins.moko.resources) apply false alias(libs.plugins.multiplatform) apply false - alias(libs.plugins.osdetector) apply false - alias(libs.plugins.protobuf) apply false - alias(libs.plugins.realm) apply false alias(libs.plugins.sekret) apply false alias(libs.plugins.serialization) apply false alias(libs.plugins.sqldelight) apply false - alias(libs.plugins.crashlytics) apply false - alias(libs.plugins.complete.kotlin) alias(libs.plugins.versions) - `project-report` } buildscript { @@ -31,12 +31,15 @@ buildscript { mavenCentral() gradlePluginPortal() maven("https://jitpack.io") - maven("https://plugins.gradle.org/m2/") maven("https://jogamp.org/deployment/maven") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } + dependencies { + classpath(libs.moko.resources.generator) + classpath(libs.atomicfu) + } } allprojects { @@ -45,7 +48,6 @@ allprojects { mavenCentral() gradlePluginPortal() maven("https://jitpack.io") - maven("https://plugins.gradle.org/m2/") maven("https://jogamp.org/deployment/maven") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://oss.sonatype.org/content/repositories/snapshots") @@ -53,20 +55,15 @@ allprojects { } tasks.withType().configureEach { - kotlinOptions.jvmTarget = CompileOptions.jvmTarget + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } } - configurations.configureEach { - exclude("androidx.palette", "palette") + plugins.withType { + yarn.yarnLockAutoReplace = true } } -tasks.withType { - projects = project.allprojects -} - -plugins.withType { - yarn.yarnLockAutoReplace = true -} tasks.withType { outputFormatter { @@ -153,7 +150,7 @@ fun isNonStable(version: String): Boolean { } tasks.create("createSekretProperties") { - var file = File(rootDir, "sekret.properties") + var file = File(project(":composeApp").projectDir, "sekret.properties") val key = properties["key"] as? String ?: return@create val value = properties["value"] as? String ?: return@create diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 9a3241c0..00000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - google() - mavenLocal() - mavenCentral() - gradlePluginPortal() - maven { url = uri("https://plugins.gradle.org/m2/") } - maven { url = uri("https://jitpack.io") } -} \ No newline at end of file diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index fa8bc749..00000000 --- a/buildSrc/settings.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/CompileOptions.kt b/buildSrc/src/main/kotlin/CompileOptions.kt deleted file mode 100644 index ec2ad040..00000000 --- a/buildSrc/src/main/kotlin/CompileOptions.kt +++ /dev/null @@ -1,19 +0,0 @@ -import org.gradle.api.JavaVersion - -object CompileOptions { - val sourceCompatibility = JavaVersion.VERSION_17 - val targetCompatibility = JavaVersion.VERSION_17 - val jvmTarget = targetCompatibility.toString() - val kotlinJdk = when { - targetCompatibility.isJava7 -> "-jdk7" - targetCompatibility.isJava8 -> "-jdk8" - else -> String() - } - val jvmTargetVersion = when { - targetCompatibility.isJava5 -> 5 - targetCompatibility.isJava6 -> 6 - targetCompatibility.isJava7 -> 7 - targetCompatibility.isJava8 -> 8 - else -> targetCompatibility.majorVersion.toIntOrNull() ?: (targetCompatibility.ordinal + 1) - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Configuration.kt b/buildSrc/src/main/kotlin/Configuration.kt deleted file mode 100644 index af444c3c..00000000 --- a/buildSrc/src/main/kotlin/Configuration.kt +++ /dev/null @@ -1,7 +0,0 @@ -object Configuration { - const val compileSdk = 34 - const val minSdk = 26 - const val targetSdk = 34 - - internal const val artifact = "dev.datlag.burningseries" -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/ExtendProject.kt b/buildSrc/src/main/kotlin/ExtendProject.kt deleted file mode 100644 index 376eaa4a..00000000 --- a/buildSrc/src/main/kotlin/ExtendProject.kt +++ /dev/null @@ -1,6 +0,0 @@ -import org.gradle.api.Project -import java.io.File - -fun Project.parentBuildDir(child: String): File { - return this.parent?.layout?.buildDirectory?.dir(child)?.get()?.asFile ?: File(projectDir, "../build/$child") -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/VersionCatalog.kt b/buildSrc/src/main/kotlin/VersionCatalog.kt deleted file mode 100644 index 3472a79d..00000000 --- a/buildSrc/src/main/kotlin/VersionCatalog.kt +++ /dev/null @@ -1,40 +0,0 @@ -import org.gradle.api.Project -import org.gradle.kotlin.dsl.getByType -import org.gradle.api.artifacts.VersionCatalog as VCatalog -import org.gradle.api.artifacts.VersionCatalogsExtension as VCatalogEx - -class VersionCatalog(project: Project) { - private val libs: VCatalog = project.extensions.getByType().named("libs") - - private fun version(key: String): String = libs.findVersion(key).get().requiredVersion - private fun versionInt(key: String): Int = version(key).getDigitsOrNull()?.toIntOrNull() ?: version(key).toInt() - - private fun String.getDigitsOrNull(): String? { - val replaced = this.replace("\\D+".toRegex(), String()) - return replaced.ifBlank { - null - } - } - - val appVersion: String - get() = version("app") - - val appVersionCode: Int - get() = versionInt("app") - - companion object { - fun artifactName(module: String = String()): String { - return if (module.isBlank()) { - Configuration.artifact - } else { - "${Configuration.artifact}.$module" - } - } - } -} - -val Project.appVersion: String - get() = VersionCatalog(this).appVersion - -val Project.appVersionCode: Int - get() = VersionCatalog(this).appVersionCode \ No newline at end of file diff --git a/cleanup.sh b/cleanup.sh deleted file mode 100755 index 6a329816..00000000 --- a/cleanup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -rm -rf .idea -./gradlew clean -rm -rf .gradle -rm -rf build -rm -rf */build -rm -rf app/iosApp/iosApp.xcworkspace -rm -rf app/iosApp/Pods -rm -rf app/iosApp/iosApp.xcodeproj/project.xcworkspace -rm -rf app/iosApp/iosApp.xcodeproj/xcuserdata diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 00000000..ac96a0b7 --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,211 @@ +import com.codingfeline.buildkonfig.compiler.FieldSpec +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +plugins { + alias(libs.plugins.aboutlibraries) + alias(libs.plugins.multiplatform) + alias(libs.plugins.android.application) + alias(libs.plugins.compose) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.konfig) + alias(libs.plugins.ktorfit) + alias(libs.plugins.moko.resources) + alias(libs.plugins.sekret) + alias(libs.plugins.serialization) +} + +val artifact = "dev.datlag.burningseries" +val appVersion = "6.0.0" +val appVersionCode = 600 + +group = artifact +version = appVersion + +multiplatformResources { + resourcesPackage.set(artifact) + resourcesClassName.set("MokoRes") +} + +composeCompiler { + enableStrongSkippingMode.set(true) + enableNonSkippingGroupOptimization.set(true) +} + +buildkonfig { + packageName = artifact + + defaultConfigs { + buildConfigField(FieldSpec.Type.STRING, "packageName", artifact) + } +} + +sekret { + properties { + enabled.set(true) + packageName.set(artifact) + + nativeCopy { + androidJNIFolder.set(project.layout.projectDirectory.dir("src/androidMain/jniLibs")) + desktopComposeResourcesFolder.set(project.layout.projectDirectory.dir("src").dir("jvmMain").dir("resources")) + } + } +} + +kotlin { + androidTarget() + jvm() + + /*listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + }*/ + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.moko.resources.compose) + + implementation(libs.kodein) + implementation(libs.kodein.compose) + + implementation(libs.haze) + implementation(libs.haze.materials) + + implementation(libs.decompose) + implementation(libs.decompose.compose) + + implementation(libs.tooling.decompose) + + implementation(libs.windowsize) + implementation(libs.ktor) + implementation(libs.ktor.content.negotiation) + implementation(libs.ktor.serialization.json) + implementation(libs.datetime) + + implementation(libs.kmpalette) + implementation(libs.kolor) + implementation(libs.kache) + implementation(libs.blurhash) + implementation(libs.qrose) + + implementation(libs.coil) + implementation(libs.coil.network) + implementation(libs.coil.svg) + implementation(libs.coil.compose) + + implementation(libs.kast) + implementation(libs.nanoid) + implementation(libs.serialization.json) + implementation(libs.serialization.protobuf) + implementation(libs.oidc) + implementation("dev.datlag.sheets-compose-dialogs:option:2.0.0-SNAPSHOT") + + implementation(project(":settings")) + implementation(project(":network")) + implementation(project(":firebase")) + implementation(project(":database")) + implementation(project(":github")) + implementation(project(":k2k")) + } + + val androidMain by getting { + apply(plugin = "kotlin-parcelize") + apply(plugin = libs.plugins.crashlytics.get().pluginId) + + dependencies { + implementation(libs.android) + implementation(libs.activity) + implementation(libs.activity.compose) + implementation(libs.multidex) + implementation(libs.androidx.window) + implementation(libs.ackpine) + + implementation(libs.ktor.jvm) + implementation(libs.coroutines.android) + implementation(libs.okhttp.doh) + + implementation(libs.bundles.android.media) + implementation(libs.webview) + implementation(libs.splashscreen) + } + } + + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + + implementation(libs.ktor.jvm) + implementation(libs.coroutines.swing) + implementation(libs.okhttp.doh) + implementation(libs.vlcj) + } + } +} + +dependencies { + coreLibraryDesugaring(libs.desugar) +} + +android { + sourceSets["main"].setRoot("src/androidMain/") + sourceSets["main"].res.srcDirs("src/androidMain/res", "src/commonMain/resources") + sourceSets["main"].assets.srcDirs("src/androidMain/assets", "src/commonMain/assets") + compileSdk = 34 + namespace = artifact + + defaultConfig { + applicationId = artifact + minSdk = 23 + targetSdk = 34 + versionCode = appVersionCode + versionName = appVersion + + multiDexEnabled = true + vectorDrawables.useSupportLibrary = true + + addManifestPlaceholders( + mapOf("oidcRedirectScheme" to "burningseries") + ) + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + merges += "values**" + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } + buildFeatures { + buildConfig = true + } +} + +compose { + desktop { + application { + mainClass = "$artifact.MainKt" + + nativeDistributions { + appResourcesRootDir.set(project.layout.projectDirectory.dir("src").dir("jvmMain").dir("resources")) + } + } + } + web { } +} diff --git a/app/shared/sekret/build.gradle.kts b/composeApp/sekret/build.gradle.kts similarity index 66% rename from app/shared/sekret/build.gradle.kts rename to composeApp/sekret/build.gradle.kts index cec4e4f2..a6e14f9f 100644 --- a/app/shared/sekret/build.gradle.kts +++ b/composeApp/sekret/build.gradle.kts @@ -1,40 +1,38 @@ plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.android.library) + alias(libs.plugins.multiplatform) + alias(libs.plugins.android.library) } -val artifact = VersionCatalog.artifactName("sekret") -group = artifact - kotlin { + jvm() androidTarget() - androidNativeX86 { + + linuxArm64 { binaries { sharedLib() } } - androidNativeX64 { + linuxX64 { binaries { sharedLib() } } - androidNativeArm32 { + mingwX64 { binaries { sharedLib() } } - androidNativeArm64 { + macosArm64 { binaries { sharedLib() } } - - jvm() - iosX64 { + macosX64 { binaries { sharedLib() } } + iosArm64 { binaries { sharedLib() @@ -45,50 +43,49 @@ kotlin { sharedLib() } } - - linuxX64 { + iosX64 { binaries { sharedLib() } } - linuxArm64 { + androidNativeX86 { binaries { sharedLib() } } - macosX64 { + androidNativeX64 { binaries { sharedLib() } } - macosArm64 { + androidNativeArm32 { binaries { sharedLib() } } - mingwX64 { + androidNativeArm64 { binaries { sharedLib() } } - jvmToolchain(CompileOptions.jvmTargetVersion) - applyDefaultHierarchyTemplate() sourceSets { - val commonMain by getting { - dependencies { - api(libs.sekret) - } + commonMain.dependencies { + api(libs.sekret) } + val jniNativeMain by creating { - nativeMain.orNull?.let { dependsOn(it) } + nativeMain.orNull?.let { dependsOn(it) } ?: dependsOn(commonMain.get()) androidNativeMain.orNull?.dependsOn(this) linuxMain.orNull?.dependsOn(this) mingwMain.orNull?.dependsOn(this) + macosMain.orNull?.dependsOn(this) } + val jniMain by creating { + dependsOn(commonMain.get()) androidMain.orNull?.dependsOn(this) jvmMain.orNull?.dependsOn(this) } @@ -96,17 +93,14 @@ kotlin { } android { - compileSdk = Configuration.compileSdk - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + compileSdk = 34 + namespace = "dev.datlag.burningseries.sekret" - namespace = artifact - - defaultConfig { - minSdk = Configuration.minSdk - } - - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } + defaultConfig { + minSdk = 23 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } } \ No newline at end of file diff --git a/app/android/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml similarity index 76% rename from app/android/src/androidMain/AndroidManifest.xml rename to composeApp/src/androidMain/AndroidManifest.xml index 45370255..0550aa2e 100644 --- a/app/android/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -1,12 +1,18 @@ - + + + + + - @@ -27,37 +33,45 @@ android:required="false"/> - - + android:theme="@style/SplashScreenTheme"> + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" + android:launchMode="singleInstance" + android:windowSoftInputMode="adjustPan" + android:supportsPictureInPicture="true" + android:visibleToInstantApps="true" + android:exported="true"> + + + + + + + + + + + + @@ -81,13 +95,12 @@ - - - - - + + @@ -106,7 +119,7 @@ { + applicationContext + } + + import(NetworkModule.di) + } + + @OptIn(DelicateCoilApi::class) + override fun onCreate() { + super.onCreate() + + if (BuildConfig.DEBUG) { + Napier.base(DebugAntilog()) + + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder() + .detectAll() + .permitDiskReads() + .permitDiskWrites() + .permitCustomSlowCalls() + .penaltyLog() + .penaltyDialog() + .build() + ) + } + StateSaver.sekretLibraryLoaded = NativeLoader.loadLibrary("sekret") + + val imageLoader by di.instance() + SingletonImageLoader.setUnsafe(imageLoader) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt new file mode 100644 index 00000000..71850c4b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/MainActivity.kt @@ -0,0 +1,189 @@ +package dev.datlag.burningseries + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.annotation.RequiresApi +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.view.WindowCompat +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.essenty.backhandler.backHandler +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.arkivanov.essenty.lifecycle.LifecycleOwner +import com.arkivanov.essenty.lifecycle.essentyLifecycle +import dev.datlag.burningseries.model.BSUtil +import dev.datlag.burningseries.other.Constants +import dev.datlag.burningseries.other.DomainVerifier +import dev.datlag.burningseries.other.DownloadManager +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.ui.custom.video.pip.enterPIPMode +import dev.datlag.burningseries.ui.custom.video.pip.isActivityStatePipMode +import dev.datlag.burningseries.ui.navigation.RootComponent +import dev.datlag.kast.Kast +import dev.datlag.kast.UnselectReason +import dev.datlag.tooling.decompose.lifecycle.LocalLifecycleOwner +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.datlag.tooling.safeCast +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import org.kodein.di.DIAware +import org.kodein.di.instance +import org.kodein.di.instanceOrNull +import org.publicvalue.multiplatform.oidc.appsupport.AndroidCodeAuthFlowFactory +import org.publicvalue.multiplatform.oidc.appsupport.CodeAuthFlowFactory + +class MainActivity : ComponentActivity() { + + private lateinit var root: RootComponent + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + installSplashScreen() + + WindowCompat.setDecorFitsSystemWindows(window, false) + enableEdgeToEdge() + + val di = applicationContext.safeCast()?.di ?: (application as DIAware).di + val lifecycleOwner = object : LifecycleOwner { + override val lifecycle: Lifecycle = essentyLifecycle() + } + val authFactory by di.instance() + val httpClient by di.instance() + val appContext by di.instanceOrNull() + + root = RootComponent( + componentContext = DefaultComponentContext( + lifecycle = lifecycleOwner.lifecycle, + backHandler = backHandler() + ), + di = di, + syncId = intent.data?.findSyncId(), + seriesHref = intent.data?.findSeries() + ) + + authFactory.registerActivity(this) + Kast.setup(this) + DownloadManager.setClient(httpClient).setFile(appContext ?: this) + DomainVerifier.verify(this) + PictureInPicture.update { this.isActivityStatePipMode() } + + setContent { + val appSettings by di.instance() + LaunchedEffect(Unit) { + appSettings.increaseStartCounter() + } + + CompositionLocalProvider( + LocalLifecycleOwner provides lifecycleOwner, + LocalEdgeToEdge provides true + ) { + App( + di = di + ) { + root.render() + } + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + val syncId = intent.data?.findSyncId() + if (!syncId.isNullOrBlank()) { + return root.onSync(syncId) + } + + val href = intent.data?.findSeries() + if (!href.isNullOrBlank()) { + return root.onSeries(href) + } + } + + override fun onUserLeaveHint() { + super.onUserLeaveHint() + enterPIPMode(this) + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration + ) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + + PictureInPicture.update { isInPictureInPictureMode } + } + + override fun onDestroy() { + super.onDestroy() + + Kast.unselect(UnselectReason.disconnected) + Kast.dispose() + DomainVerifier.verify(this) + + PictureInPicture.update { this.isActivityStatePipMode() } + } + + override fun onStart() { + super.onStart() + + DomainVerifier.verify(this) + PictureInPicture.update { this.isActivityStatePipMode() } + } + + override fun onResume() { + super.onResume() + + DomainVerifier.verify(this) + PictureInPicture.update { this.isActivityStatePipMode() } + } + + override fun onPause() { + super.onPause() + + DomainVerifier.verify(this) + PictureInPicture.update { this.isActivityStatePipMode() } + } + + override fun onRestart() { + super.onRestart() + + DomainVerifier.verify(this) + PictureInPicture.update { this.isActivityStatePipMode() } + } + + private fun Uri.findSyncId(): String? { + val matchingHost = this.host.equals(Constants.SYNCING_DOMAIN, ignoreCase = true) + if (matchingHost) { + val matchingPath = this.pathSegments.firstOrNull()?.equals("sync", ignoreCase = true) == true + + if (matchingPath) { + val id = lastPathSegment?.ifBlank { + null + } ?: pathSegments.lastOrNull()?.ifBlank { + null + } + + return id + } + } + + return null + } + + private fun Uri.findSeries(): String? { + return BSUtil.matchingUrl(this.toString(), this.path)?.let(BSUtil::fixSeriesHref)?.ifBlank { null } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.android.kt new file mode 100644 index 00000000..9c049e60 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.android.kt @@ -0,0 +1,89 @@ +package dev.datlag.burningseries.common + +import android.content.Context +import android.os.Build +import android.provider.Settings +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalContext +import com.vanniktech.blurhash.BlurHash +import dev.datlag.tooling.Platform +import dev.datlag.tooling.async.scopeCatching +import dev.datlag.tooling.systemEnv +import dev.datlag.tooling.systemProperty + +actual fun BlurHash.decode( + hash: String?, + width: Int, + height: Int +): ImageBitmap? { + if (hash.isNullOrBlank()) { + return null + } + + val bitmap = decode( + blurHash = hash, + width = width, + height = height + ) + return bitmap?.asImageBitmap() +} + +@Composable +actual fun Modifier.drawProgress(color: Color, progress: Float): Modifier = drawWithContent { + with(drawContext.canvas.nativeCanvas) { + val checkPoint = saveLayer(null, null) + + drawContent() + + drawRect( + color = color, + size = Size(size.width * progress, size.height), + blendMode = BlendMode.SrcOut + ) + + restoreToCount(checkPoint) + } +} + +@Composable +actual fun Platform.rememberIsTv(): Boolean { + val context = LocalContext.current + return remember(context) { + isTelevision(context) + } +} + +fun Platform.deviceName(context: Context): String { + val settingsName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + scopeCatching { + Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) + }.getOrNull()?.ifBlank { null } + } else { + null + } + + if (!settingsName.isNullOrBlank()) { + return settingsName + } + + val bluetoothName = scopeCatching { + Settings.Secure.getString(context.contentResolver, "bluetooth_name") + }.getOrNull()?.ifBlank { null } + + if (!bluetoothName.isNullOrBlank()) { + return bluetoothName + } + + return systemEnv("HOSTNAME")?.ifBlank { null } + ?: systemProperty("HOSTNAME")?.ifBlank { null } + ?: Build.MODEL +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/module/PlatformModule.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/module/PlatformModule.android.kt new file mode 100644 index 00000000..42fb1f76 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/module/PlatformModule.android.kt @@ -0,0 +1,184 @@ +package dev.datlag.burningseries.module + +import android.content.Context +import android.os.Build +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.okio.OkioStorage +import coil3.ImageLoader +import coil3.request.allowHardware +import dev.datlag.burningseries.BuildConfig +import dev.datlag.burningseries.BuildKonfig +import dev.datlag.burningseries.Sekret +import dev.datlag.burningseries.common.deviceName +import dev.datlag.burningseries.database.DriverFactory +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.firebase.initialize +import dev.datlag.burningseries.other.StateSaver +import dev.datlag.burningseries.settings.DataStoreAppSettings +import dev.datlag.burningseries.settings.DataStoreUserSettings +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.AppSettings +import dev.datlag.burningseries.settings.model.UserSettings +import dev.datlag.tooling.Platform +import dev.datlag.tooling.createAsFileSafely +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.json.json +import okhttp3.Dns +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.dnsoverhttps.DnsOverHttps +import okio.FileSystem +import okio.Path.Companion.toOkioPath +import org.kodein.di.DI +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import org.publicvalue.multiplatform.oidc.appsupport.AndroidCodeAuthFlowFactory +import org.publicvalue.multiplatform.oidc.appsupport.CodeAuthFlowFactory +import java.net.InetAddress +import java.util.concurrent.TimeUnit + +actual object PlatformModule { + + private const val NAME = "AndroidPlatformModule" + private const val APP_VERSION = "APP_VERSION" + private const val DEVICE_NAME = "DEVICE_NAME" + + actual val di: DI.Module = DI.Module(NAME) { + bindSingleton { + OkHttpClient.Builder() + .followRedirects(true) + .followSslRedirects(true) + .connectTimeout(3, TimeUnit.MINUTES) + .readTimeout(3, TimeUnit.MINUTES) + .writeTimeout(3, TimeUnit.MINUTES) + .build() + } + bindSingleton { + DnsOverHttps.Builder() + .client(instance()) + .url("https://dns.google/dns-query".toHttpUrl()) + .bootstrapDnsHosts(InetAddress.getByName("8.8.4.4"), InetAddress.getByName("8.8.8.8")) + .build() + } + bindSingleton { + HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + connectTimeout(3, TimeUnit.MINUTES) + readTimeout(3, TimeUnit.MINUTES) + writeTimeout(3, TimeUnit.MINUTES) + dns(instance()) + } + } + install(ContentNegotiation) { + json(instance(), ContentType.Application.Json) + json(instance(), ContentType.Text.Plain) + } + } + } + bindSingleton("STREAM_CLIENT") { + HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + } + } + } + } + bindSingleton> { + val app: Context = instance() + + DataStoreFactory.create( + storage = OkioStorage( + fileSystem = FileSystem.SYSTEM, + serializer = AppSettings.SettingsSerializer, + producePath = { + val path = app.filesDir.toOkioPath() + .resolve("v6") + .resolve("app.settings").also { + it.toFile().createAsFileSafely() + } + + path + } + ) + ) + } + bindSingleton { + DataStoreAppSettings(instance()) + } + bindSingleton> { + val app: Context = instance() + + DataStoreFactory.create( + storage = OkioStorage( + fileSystem = FileSystem.SYSTEM, + serializer = UserSettings.SettingsSerializer, + producePath = { + val path = app.filesDir.toOkioPath() + .resolve("v6") + .resolve("user.settings").also { + it.toFile().createAsFileSafely() + } + + path + } + ) + ) + } + bindSingleton { + DataStoreUserSettings(instance()) + } + bindSingleton { + if (StateSaver.sekretLibraryLoaded) { + FirebaseFactory.initialize( + context = instance(), + projectId = Sekret.firebaseProject(BuildKonfig.packageName), + applicationId = Sekret.firebaseApplication(BuildKonfig.packageName)!!, + apiKey = Sekret.firebaseApiKey(BuildKonfig.packageName)!!, + localLogger = object : FirebaseFactory.Crashlytics.LocalLogger { + override fun warn(message: String?) { + message?.let { Napier.w(it) } + } + + override fun error(message: String?) { + message?.let { Napier.e(it) } + } + + override fun error(throwable: Throwable?) { + throwable?.let { Napier.e("", it) } + } + } + ) + } else { + FirebaseFactory.Empty + } + } + bindSingleton { + DriverFactory( + context = instance() + ) + } + bindSingleton { + AndroidCodeAuthFlowFactory() + } + if (!BuildConfig.DEBUG) { + bindSingleton(APP_VERSION) { + BuildConfig.VERSION_NAME + } + } + bindSingleton(DEVICE_NAME) { + Platform.deviceName(instance()) + } + } +} + +actual fun ImageLoader.Builder.extendImageLoader(): ImageLoader.Builder { + return this.allowHardware(false) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/AniFlow.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/AniFlow.android.kt new file mode 100644 index 00000000..db82aa49 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/AniFlow.android.kt @@ -0,0 +1,20 @@ +package dev.datlag.burningseries.other + +import android.content.pm.PackageManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext + +@Composable +actual fun AniFlow.isInstalled(): Boolean { + val context = LocalContext.current + + return remember(context) { + try { + context.packageManager.getPackageInfo(this.packageName, 0) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } +} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/CastOptionsProvider.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/CastOptionsProvider.kt similarity index 95% rename from app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/CastOptionsProvider.kt rename to composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/CastOptionsProvider.kt index 54b796e6..1b6af7cf 100644 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/CastOptionsProvider.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/CastOptionsProvider.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared +package dev.datlag.burningseries.other import android.content.Context import androidx.media3.cast.DefaultCastOptionsProvider diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DatabaseProvider.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DatabaseProvider.kt similarity index 93% rename from app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DatabaseProvider.kt rename to composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DatabaseProvider.kt index 01e902b0..01ee6094 100644 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DatabaseProvider.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DatabaseProvider.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared.other +package dev.datlag.burningseries.other import android.content.ContentProvider import android.content.ContentValues @@ -26,7 +26,6 @@ class DatabaseProvider : ContentProvider() { "series/*", SERIES_URI_CODE ) - uriMatcher.addURI( PROVIDER_NAME, "episodes", @@ -94,7 +93,12 @@ class DatabaseProvider : ContentProvider() { return 0 } - override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { return 0 } diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DomainVerifier.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DomainVerifier.kt similarity index 60% rename from app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DomainVerifier.kt rename to composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DomainVerifier.kt index 777fa299..53242115 100644 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/other/DomainVerifier.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DomainVerifier.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared.other +package dev.datlag.burningseries.other import android.content.Context import android.content.Intent @@ -16,22 +16,30 @@ data object DomainVerifier { @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) val supported: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S - val verified: MutableStateFlow = MutableStateFlow(false) - @Suppress("NewApi") + val syncingEnabled: MutableStateFlow = MutableStateFlow(false) + val linksSupported: MutableStateFlow = MutableStateFlow(false) + fun verify(context: Context) { if (supported) { val manager = ContextCompat.getSystemService(context, DomainVerificationManager::class.java) val userState = manager?.getDomainVerificationUserState(context.packageName) - val unapprovedDomains = userState?.hostToStateMap?.filterValues { it == DomainVerificationUserState.DOMAIN_STATE_NONE } - unapprovedDomains?.let { count -> - verified.update { count.isEmpty() } + val syncDomain = userState?.hostToStateMap?.get(Constants.SYNCING_DOMAIN) ?: DomainVerificationUserState.DOMAIN_STATE_NONE + syncingEnabled.update { + syncDomain != DomainVerificationUserState.DOMAIN_STATE_NONE + } + + val unapproved = userState?.hostToStateMap?.filterKeys { + !it.equals(Constants.SYNCING_DOMAIN, ignoreCase = true) + }?.filterValues { it == DomainVerificationUserState.DOMAIN_STATE_NONE } + + unapproved?.let { count -> + linksSupported.update { count.isEmpty() } } } } - @Suppress("NewApi") fun enable(context: Context) { if (supported) { val intent = Intent( diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DownloadManager.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DownloadManager.kt new file mode 100644 index 00000000..fb8f3456 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/other/DownloadManager.kt @@ -0,0 +1,127 @@ +package dev.datlag.burningseries.other + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.PendingIntentCompat +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import dev.datlag.burningseries.github.model.Asset +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.tooling.async.suspendCatching +import dev.datlag.tooling.compose.launchIO +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.createAsFileSafely +import dev.datlag.tooling.deleteSafely +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.onDownload +import io.ktor.client.request.get +import io.ktor.client.request.prepareGet +import io.ktor.client.statement.bodyAsChannel +import io.ktor.utils.io.core.isEmpty +import io.ktor.utils.io.core.readBytes +import io.ktor.utils.io.core.use +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.serialization.Serializable +import okhttp3.internal.closeQuietly +import okio.Path.Companion.toOkioPath +import ru.solrudev.ackpine.DisposableSubscription +import ru.solrudev.ackpine.DisposableSubscriptionContainer +import ru.solrudev.ackpine.installer.InstallFailure +import ru.solrudev.ackpine.installer.PackageInstaller +import ru.solrudev.ackpine.installer.createSession +import ru.solrudev.ackpine.session.SessionResult +import ru.solrudev.ackpine.session.await +import ru.solrudev.ackpine.session.parameters.Confirmation +import ru.solrudev.ackpine.session.progress +import java.io.File + +data object DownloadManager { + + private const val BUFFER_SIZE = 1024L + + private lateinit var httpClient: HttpClient + private lateinit var file: File + + private val _progress = MutableStateFlow(Progress(0, 0)) + val progress: StateFlow = _progress + + private val _downloadEnabled = MutableStateFlow(true) + val downloadEnabled: StateFlow = _downloadEnabled + + fun setClient(httpClient: HttpClient) = apply { + this.httpClient = httpClient + } + + fun setFile(context: Context) = apply { + setFile(context.filesDir.toOkioPath().resolve("v6").resolve("update.apk").toFile()) + } + + fun setFile(file: File) = apply { + this.file = file + this.file.deleteSafely() + } + + suspend fun download( + asset: Asset + ): File? = withIOContext { + if (!::httpClient.isInitialized) { + return@withIOContext null + } + + _downloadEnabled.update { false } + file.deleteSafely() + file.createAsFileSafely() + + httpClient.prepareGet(asset.downloadUrl) { + onDownload { bytesSentTotal, contentLength -> + _progress.update { + Progress( + reached = bytesSentTotal, + length = contentLength + ) + } + } + }.execute { response -> + val channel = response.bodyAsChannel() + + while (!channel.isClosedForRead) { + val packet = channel.readRemaining(BUFFER_SIZE) + + while (!packet.isEmpty) { + val bytes = packet.readBytes() + + file.appendBytes(bytes) + } + } + } + + _downloadEnabled.update { true } + return@withIOContext file + } + + suspend fun install(context: Context, file: File = this.file): SessionResult { + val packageInstaller = PackageInstaller.getInstance(context) + return suspendCatching { + packageInstaller.createSession(file.toUri()) { + confirmation = Confirmation.IMMEDIATE + }.await() + }.getOrNull() ?: SessionResult.Error(InstallFailure.Generic()) + } + + @Serializable + data class Progress( + val reached: Long, + val length: Long + ) { + val percentage: Float = if (length > reached) reached.toFloat() / length.toFloat() else 0F + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/theme/Theme.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/theme/Theme.android.kt new file mode 100644 index 00000000..cc3aacaf --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/theme/Theme.android.kt @@ -0,0 +1,19 @@ +package dev.datlag.burningseries.theme + +import android.app.Activity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowInsetsControllerCompat + +@Composable +internal fun SystemAppearance(isDark: Boolean) { + val view = LocalView.current + LaunchedEffect(isDark) { + val window = (view.context as Activity).window + WindowInsetsControllerCompat(window, window.decorView).apply { + isAppearanceLightStatusBars = isDark + isAppearanceLightNavigationBars = isDark + } + } +} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.android.kt similarity index 57% rename from app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.android.kt rename to composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.android.kt index afd68654..e25ebcbd 100644 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/custom/VerticalScrollbar.android.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.android.kt @@ -1,14 +1,27 @@ -package dev.datlag.burningseries.shared.ui.custom +package dev.datlag.burningseries.ui.custom import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp @Composable -actual fun VerticalScrollbar(adapter: ScrollbarAdapter, modifier: Modifier) { -} +actual fun VerticalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier, + style: ScrollbarStyle +) { } + +@Composable +actual fun HorizontalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier, + style: ScrollbarStyle +) { } @Composable actual fun rememberScrollbarAdapter( @@ -26,6 +39,7 @@ actual interface ScrollbarAdapter { actual val scrollOffset: Double actual val contentSize: Double actual val viewportSize: Double + actual suspend fun scrollTo(scrollOffset: Double) } @@ -38,4 +52,16 @@ private val DEFAULT_SCROLLBAR_ADAPTER = object : ScrollbarAdapter { get() = 0.0 override suspend fun scrollTo(scrollOffset: Double) { } +} + +@Composable +actual fun localScrollbarStyle(): ScrollbarStyle { + return ScrollbarStyle( + minimalHeight = 16.dp, + thickness = 8.dp, + shape = RoundedCornerShape(4.dp), + hoverDurationMillis = 300, + unhoverColor = Color.Black.copy(alpha = 0.12f), + hoverColor = Color.Black.copy(alpha = 0.50f) + ) } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.android.kt new file mode 100644 index 00000000..40f6e32c --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.android.kt @@ -0,0 +1,24 @@ +package dev.datlag.burningseries.ui.custom + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext + +@Composable +actual fun AndroidFixWindowSize(content: @Composable () -> Unit) { + // Fix for androidx.window crash prior 1.3.0 + CompositionLocalProvider( + LocalContext provides (LocalContext.current.findActivity() ?: LocalContext.current) + ) { + content() + } +} + +internal tailrec fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/video/pip/PictureInPicture.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/video/pip/PictureInPicture.kt new file mode 100644 index 00000000..7746921d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/custom/video/pip/PictureInPicture.kt @@ -0,0 +1,54 @@ +package dev.datlag.burningseries.ui.custom.video.pip + +import android.app.PictureInPictureParams +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.util.Rational +import androidx.annotation.ChecksSdkIntAtLeast +import androidx.media3.ui.PlayerView +import dev.datlag.burningseries.ui.custom.findActivity + +internal fun enterPIPMode(context: Context) { + if (context.hasPIPFeature()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + var params = PictureInPictureParams.Builder() + .setAspectRatio( + Rational(19, 9) + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + params = params.setSeamlessResizeEnabled(true).setAutoEnterEnabled(true) + } + + context.findActivity()?.enterPictureInPictureMode(params.build()) + } else { + context.findActivity()?.enterPictureInPictureMode() + } + } +} + +@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N) +internal fun Context.hasPIPFeature(): Boolean { + val pm = this.packageManager ?: return false + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + || if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.hasSystemFeature(PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE) + } else { + false + } + } else { + false + } +} + +internal fun Context.isActivityStatePipMode(): Boolean { + val currentActivity = findActivity() ?: return false + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + currentActivity.isInPictureInPictureMode + } else { + false + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.android.kt new file mode 100644 index 00000000..52c2382b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.android.kt @@ -0,0 +1,89 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import android.webkit.WebView +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.kevinnzou.web.WebView +import com.kevinnzou.web.rememberWebViewState +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.model.BSUtil +import dev.datlag.tooling.compose.launchIO +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.compose.withMainContext +import io.github.aakira.napier.Napier +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@OptIn(ExperimentalResourceApi::class) +@Composable +actual fun ActivateScreen(component: ActivateComponent) { + val dialogState by component.dialog.subscribeAsState() + + dialogState.child?.instance?.render() + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + Toolbar(component) + } + ) { padding -> + val state = rememberWebViewState(BSUtil.getBurningSeriesLink(component.episode.href)) + val scope = rememberCoroutineScope() + + WebView( + state = state, + modifier = Modifier.padding(padding).fillMaxSize(), + captureBackPresses = true, + client = WebViewClient( + allowedHosts = persistentSetOf(BSUtil.HOST_BS_TO) + ), + onCreated = { + it.settings.allowFileAccess = false + it.settings.javaScriptEnabled = true + it.settings.javaScriptCanOpenWindowsAutomatically = false + it.settings.mediaPlaybackRequiresUserGesture = true + + scope.launchIO { + it.scrape( + js = String(Res.readBytes("files/scrape_hoster_android.js")), + onScraped = component::onScraped + ) + } + } + ) + } +} + +private suspend fun WebView.scrape(js: String?, onScraped: (String?, String?) -> Unit) { + while (currentCoroutineContext().isActive && !js.isNullOrBlank()) { + delay(3000) + withMainContext { + val episodeHref = BSUtil.matchingUrl( + this@scrape.url, + this@scrape.originalUrl + ) + + this@scrape.evaluateJavascript(js) { result -> + val episode = episodeHref ?: BSUtil.matchingUrl( + this@scrape.url, + this@scrape.originalUrl + ) + onScraped(episode, result) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/Toolbar.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/Toolbar.kt new file mode 100644 index 00000000..9a766926 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/Toolbar.kt @@ -0,0 +1,88 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Save +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.load_to_activate_episode +import dev.datlag.burningseries.network.state.SaveState +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun Toolbar(component: ActivateComponent) { + TopAppBar( + navigationIcon = { + IconButton( + onClick = { + component.back() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + }, + title = { + Text( + text = stringResource(Res.string.load_to_activate_episode) + ) + }, + actions = { + val saveState by component.saveState.collectAsStateWithLifecycle() + when (val current = saveState) { + is SaveState.Success -> { + component.success( + current.series, + current.episode, + current.stream + ) + } + is SaveState.Error -> { + component.error( + current.series, + current.episode, + current.stream + ) + } + else -> { } + } + + val isSaving = remember(saveState) { + when (saveState) { + is SaveState.Saving -> true + else -> false + } + } + + AnimatedVisibility( + visible = isSaving, + enter = fadeIn(), + exit = fadeOut() + ) { + IconButton( + onClick = {} + ) { + Icon( + imageVector = Icons.Rounded.Save, + contentDescription = null + ) + } + } + } + ) +} \ No newline at end of file diff --git a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebViewClient.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/WebViewClient.kt similarity index 59% rename from app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebViewClient.kt rename to composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/WebViewClient.kt index eaa3648c..a81cc9b3 100644 --- a/app/shared/src/androidMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/activate/component/WebViewClient.kt +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/WebViewClient.kt @@ -1,11 +1,13 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.activate.component +package dev.datlag.burningseries.ui.navigation.screen.activate import android.webkit.WebResourceRequest import android.webkit.WebView -import com.google.accompanist.web.AccompanistWebViewClient +import com.kevinnzou.web.AccompanistWebViewClient +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf data class WebViewClient( - private val allowedHosts: Set = setOf() + private val allowedHosts: ImmutableSet = persistentSetOf() ) : AccompanistWebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { return if (request?.url?.host != null) { diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.android.kt new file mode 100644 index 00000000..47a47fcd --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.android.kt @@ -0,0 +1,56 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.Image +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.core.content.ContextCompat +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.other.AniFlow +import dev.datlag.burningseries.other.isInstalled +import dev.datlag.tooling.Platform +import dev.icerock.moko.resources.compose.painterResource + +@Composable +actual fun AniFlowIconButton(onClick: () -> Unit) { + if (Platform.rememberIsTv()) { + IconButton( + onClick = onClick + ) { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + } + } else { + val context = LocalContext.current + val uriHandler = LocalUriHandler.current + val installed = AniFlow.isInstalled() + + IconButton( + onClick = { + if (installed) { + val intent = context.packageManager.getLaunchIntentForPackage(AniFlow.packageName) + + if (intent != null) { + ContextCompat.startActivity(context, intent, null) + } else { + uriHandler.openUri(AniFlow.googlePlay) + } + } else { + uriHandler.openUri(AniFlow.googlePlay) + } + } + ) { + Image( + painter = painterResource(MokoRes.images.AniFlow), + contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.android.kt new file mode 100644 index 00000000..fb267dc5 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.android.kt @@ -0,0 +1,75 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.enable +import dev.datlag.burningseries.composeapp.generated.resources.open_domains_text_1 +import dev.datlag.burningseries.composeapp.generated.resources.open_domains_text_2 +import dev.datlag.burningseries.composeapp.generated.resources.open_domains_text_3 +import dev.datlag.burningseries.composeapp.generated.resources.open_domains_title +import dev.datlag.burningseries.other.DomainVerifier +import dev.datlag.tooling.Platform +import kotlinx.coroutines.flow.StateFlow +import org.jetbrains.compose.resources.stringResource + +actual val Platform.linksSupported: StateFlow + get() = DomainVerifier.linksSupported + +actual fun LazyListScope.LinkSupportSection( + headerPadding: PaddingValues +) { + if (DomainVerifier.supported) { + item { + Text( + modifier = Modifier.padding(headerPadding), + text = stringResource(Res.string.open_domains_title), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } + item { + Column( + modifier = Modifier.fillParentMaxWidth().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) + ) { + val context = LocalContext.current + + SideEffect { + DomainVerifier.verify(context) + } + + Text( + text = buildString { + append(stringResource(Res.string.open_domains_text_1)) + appendLine() + append(stringResource(Res.string.open_domains_text_2)) + appendLine() + append(stringResource(Res.string.open_domains_text_3)) + } + ) + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { + DomainVerifier.enable(context) + } + ) { + Text(text = stringResource(Res.string.enable)) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.android.kt new file mode 100644 index 00000000..5b4b7a8d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.android.kt @@ -0,0 +1,192 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.release + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +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.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Download +import androidx.compose.material.icons.rounded.SystemUpdate +import androidx.compose.material.icons.rounded.SystemUpdateAlt +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.common.drawProgress +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.install +import dev.datlag.burningseries.composeapp.generated.resources.release_text_1 +import dev.datlag.burningseries.composeapp.generated.resources.release_text_2 +import dev.datlag.burningseries.composeapp.generated.resources.view +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.other.Constants +import dev.datlag.burningseries.other.DownloadManager +import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.launchIO +import dev.datlag.tooling.compose.withMainContext +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource +import ru.solrudev.ackpine.session.SessionResult + +@Composable +actual fun ReleaseDialog(component: ReleaseComponent) { + val androidAsset = component.release.androidAsset + val isDraftOrPreRelease = remember(component.release) { component.release.isDraft || component.release.isPrerelease } + var dismissRequests by remember(isDraftOrPreRelease) { mutableIntStateOf(if (isDraftOrPreRelease) 5 else 0) } + + AlertDialog( + onDismissRequest = { + dismissRequests++ + if (dismissRequests >= 5) { + component.dismiss() + } + }, + icon = { + Icon( + imageVector = DeviceIcon, + contentDescription = null + ) + }, + title = { + Text(text = component.release.title ?: component.release.tagName) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.release_text_1, component.release.tagName), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.release_text_2), + textAlign = TextAlign.Center + ) + } + }, + dismissButton = if (androidAsset != null) { + { + ViewButton(component.release) + } + } else null, + confirmButton = { + if (androidAsset == null) { + ViewButton(component.release) + } else { + val progress by DownloadManager.progress.collectAsStateWithLifecycle() + val installEnabled by DownloadManager.downloadEnabled.collectAsStateWithLifecycle() + val uriHandler = LocalUriHandler.current + val context = LocalContext.current + val scope = rememberCoroutineScope() + + TextButton( + onClick = { + scope.launchIO { + val file = DownloadManager.download(androidAsset) ?: return@launchIO + + when (DownloadManager.install(context, file)) { + is SessionResult.Success -> { + withMainContext { + component.dismiss() + } + } + is SessionResult.Error -> { + uriHandler.openUri(component.release.url ?: Constants.GITHUB_RELEASE) + } + } + } + }, + enabled = installEnabled, + contentPadding = PaddingValues(0.dp), + colors = ButtonDefaults.textButtonColors( + disabledContainerColor = Color.Transparent, + disabledContentColor = MaterialTheme.colorScheme.secondary + ) + ) { + Row( + modifier = Modifier + .defaultMinSize( + minWidth = ButtonDefaults.MinWidth, + minHeight = ButtonDefaults.MinHeight + ) + .clip(CircleShape) + .drawProgress(LocalContentColor.current, progress.percentage) + .padding(8.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.Download, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.install)) + } + } + } + } + ) +} + +@Composable +private fun ViewButton(release: UserAndRelease.Release) { + val uriHandler = LocalUriHandler.current + + TextButton( + onClick = { + uriHandler.openUri(release.url ?: Constants.GITHUB_RELEASE) + } + ) { + Image( + modifier = Modifier.size(ButtonDefaults.IconSize), + painter = painterResource(MokoRes.images.github), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.view)) + } +} + +internal actual val DeviceIcon: ImageVector + @Composable + get() = if (Platform.rememberIsTv()) { + Icons.Rounded.SystemUpdateAlt + } else { + Icons.Rounded.SystemUpdate + } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.android.kt new file mode 100644 index 00000000..053fb6e8 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.android.kt @@ -0,0 +1,70 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Phonelink +import androidx.compose.material3.Icon +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchColors +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.common.alwaysEnabledColors +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.syncing_enabled +import dev.datlag.burningseries.other.DomainVerifier +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource + +actual fun LazyListScope.SyncSection() { + if (DomainVerifier.supported) { + item { + val context = LocalContext.current + val enabled by DomainVerifier.syncingEnabled.collectAsStateWithLifecycle() + + SideEffect { + DomainVerifier.verify(context) + } + + Row( + modifier = Modifier.fillParentMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Rounded.Phonelink, + contentDescription = null, + ) + Text(text = stringResource(Res.string.syncing_enabled)) + Spacer(modifier = Modifier.weight(1F)) + Switch( + checked = enabled, + onCheckedChange = { + DomainVerifier.enable(context) + }, + enabled = !enabled, + colors = SwitchDefaults.alwaysEnabledColors(), + thumbContent = { + if (enabled) { + Icon( + modifier = Modifier.size(SwitchDefaults.IconSize), + imageVector = Icons.Rounded.Check, + contentDescription = null + ) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt new file mode 100644 index 00000000..64025cd0 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/BottomControls.kt @@ -0,0 +1,99 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsDraggedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.safeGesturesPadding +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +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.media3.common.util.UnstableApi +import dev.datlag.burningseries.common.toDuration +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import io.github.aakira.napier.Napier +import kotlinx.coroutines.flow.StateFlow + +@androidx.annotation.OptIn(UnstableApi::class) +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BottomControls( + isVisible: Boolean, + playerWrapper: PlayerWrapper, + modifier: Modifier = Modifier, +) { + val isFinished by playerWrapper.isFinished.collectAsStateWithLifecycle() + val progress by playerWrapper.progress.collectAsStateWithLifecycle() + val length by playerWrapper.length.collectAsStateWithLifecycle() + + AnimatedVisibility( + modifier = modifier.safeDrawingPadding(), + visible = isVisible || isFinished, + enter = slideInVertically { it / 2 } + fadeIn(), + exit = slideOutVertically { it / 2 } + fadeOut() + ) { + BottomAppBar( + modifier = modifier, + containerColor = Color.Black.copy(alpha = 0.5F), + contentColor = Color.White + ) { + val source = remember { MutableInteractionSource() } + val dragging by source.collectIsDraggedAsState() + var changingProgress by remember { mutableLongStateOf(progress) } + val displayProgress = remember(dragging, progress, changingProgress) { + if (dragging) { + changingProgress + } else { + progress + } + } + + Text( + text = displayProgress.toDuration(), + maxLines = 1 + ) + Slider( + modifier = Modifier.weight(1F), + value = displayProgress.toFloat(), + valueRange = 0F..length.toFloat(), + onValueChange = { + if (dragging) { + changingProgress = it.toLong() + playerWrapper.showControls() + } + }, + onValueChangeFinished = { + if (dragging) { + playerWrapper.seekTo(changingProgress) + } + }, + interactionSource = source + ) + Text( + text = length.toDuration(), + maxLines = 1 + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt new file mode 100644 index 00000000..35d12767 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/CenterControls.kt @@ -0,0 +1,149 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.annotation.OptIn +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.FastForward +import androidx.compose.material.icons.rounded.FastRewind +import androidx.compose.material.icons.rounded.Pause +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.SkipNext +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import androidx.media3.common.util.UnstableApi +import dev.datlag.burningseries.common.drawProgress +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.skeo.DirectLink +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.ImmutableCollection + +@OptIn(UnstableApi::class) +@Composable +fun CenterControls( + isVisible: Boolean, + isPlaying: Boolean, + nextState: EpisodeState, + playerWrapper: PlayerWrapper, + modifier: Modifier = Modifier, + onReplayClick: () -> Unit, + onPauseToggle: () -> Unit, + onForwardClick: () -> Unit, + onNext: (Series.Episode, ImmutableCollection) -> Unit +) { + val isFinished by playerWrapper.isFinished.collectAsStateWithLifecycle() + + AnimatedVisibility( + modifier = modifier, + visible = isVisible || isFinished, + enter = fadeIn(), + exit = fadeOut() + ) { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + modifier = Modifier.background( + color = Color.Black.copy(alpha = 0.5F), + shape = CircleShape + ), + onClick = onReplayClick + ) { + Icon( + imageVector = Icons.Rounded.FastRewind, + contentDescription = null, + tint = Color.White + ) + } + + IconButton( + modifier = Modifier.background( + color = Color.Black.copy(alpha = 0.5F), + shape = CircleShape + ), + onClick = onPauseToggle + ) { + Icon( + imageVector = if (isPlaying) Icons.Rounded.Pause else Icons.Rounded.PlayArrow, + contentDescription = null, + tint = Color.White + ) + } + + val animatedProgress by animateFloatAsState( + targetValue = if (isFinished && nextState is EpisodeState.SuccessStream) 1F else 0F, + animationSpec = tween( + durationMillis = 10000, + easing = LinearEasing + ), + finishedListener = { + val success = (nextState as? EpisodeState.SuccessStream) + + success?.let { + onNext(success.episode, success.results) + } + } + ) + if (isFinished && nextState is EpisodeState.SuccessStream) { + IconButton( + modifier = Modifier.background( + color = Color.Black.copy(alpha = 0.5F), + shape = CircleShape + ), + onClick = { + onNext(nextState.episode, nextState.results) + } + ) { + Icon( + modifier = Modifier.fillMaxSize().drawProgress(Color.White, animatedProgress).padding(8.dp), + imageVector = Icons.Rounded.SkipNext, + contentDescription = null, + tint = Color.White + ) + } + } else { + IconButton( + modifier = Modifier.background( + color = Color.Black.copy(alpha = 0.5F), + shape = CircleShape + ), + onClick = onForwardClick, + enabled = !isFinished + ) { + Icon( + imageVector = Icons.Rounded.FastForward, + contentDescription = null, + tint = Color.White + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/PlayerWrapper.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/PlayerWrapper.kt new file mode 100644 index 00000000..3ab026f8 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/PlayerWrapper.kt @@ -0,0 +1,492 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import android.app.Notification +import android.app.NotificationManager +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Icon +import android.media.MediaSession2Service +import android.media.session.MediaSession.Token +import android.os.Build +import android.support.v4.media.session.MediaSessionCompat +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.nativeKeyCode +import androidx.compose.ui.input.key.type +import androidx.core.app.NotificationChannelCompat +import androidx.core.app.NotificationCompat +import androidx.media3.cast.CastPlayer +import androidx.media3.cast.SessionAvailabilityListener +import androidx.media3.common.ForwardingPlayer +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.MimeTypes +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import androidx.media3.common.util.NotificationUtil +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.datasource.HttpDataSource +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.extractor.DefaultExtractorsFactory +import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES +import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS +import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS +import androidx.media3.session.MediaNotification +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper +import androidx.media3.session.MediaStyleNotificationHelper.MediaStyle +import coil3.annotation.InternalCoilApi +import com.google.android.gms.cast.framework.CastContext +import com.google.android.gms.cast.framework.CastState +import dev.datlag.burningseries.R +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.nanoid.NanoIdUtils +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.compose.withMainContext +import dev.datlag.tooling.scopeCatching +import io.github.aakira.napier.Napier +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.updateAndGet +import kotlinx.coroutines.isActive +import kotlinx.datetime.Clock +import kotlin.math.max +import kotlin.random.Random +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +@UnstableApi +class PlayerWrapper( + private val context: Context, + castContext: CastContext?, + private val startingPos: Long, + private val startingLength: Long, + private val headers: ImmutableMap, + private val coverData: ByteArray?, + private val longTimeout: Boolean, + private val onError: (PlaybackException) -> Unit = { }, + private val onFirstFrame: () -> Unit = { }, + private val onProgressChange: (Long) -> Unit = { }, + private val onLengthChange: (Long) -> Unit = { }, + private val onFinish: () -> Unit = { } +): SessionAvailabilityListener, Player.Listener { + + private val extractorFactory = DefaultExtractorsFactory().setTsExtractorFlags( + FLAG_ALLOW_NON_IDR_KEYFRAMES and FLAG_DETECT_ACCESS_UNITS and FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS + ) + + private val dataSource: DataSource.Factory = DefaultHttpDataSource.Factory() + .setDefaultRequestProperties(headers) + .setAllowCrossProtocolRedirects(true) + .setKeepPostFor302Redirects(true) + .setReadTimeoutMs( + if (longTimeout) { + 30.seconds.inWholeMilliseconds.toInt() + } else { + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS + } + ).setConnectTimeoutMs( + if (longTimeout) { + 30.seconds.inWholeMilliseconds.toInt() + } else { + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS + } + ) + + private val castPlayer = castContext?.let(::CastPlayer) + + private val localPlayer = ExoPlayer.Builder(context).apply { + setSeekBackIncrementMs(10000) + setSeekForwardIncrementMs(10000) + setMediaSourceFactory( + DefaultMediaSourceFactory( + DefaultDataSource.Factory(context, dataSource), + extractorFactory + ) + ) + }.build() + + private val castState = castContext?.castState + private val casting = castState == CastState.CONNECTED + private var castSupported = false + set(value) { + field = value + + if (value && requestCasting) { + useCastPlayer = true + } + } + private var requestCasting = casting + set(value) { + field = value + + if (castSupported && value) { + useCastPlayer = true + } + } + private var useCastPlayer = castSupported && requestCasting + set(value) { + val previous = field + field = value + + if (value != previous) { + player = if (value) { + castPlayer.also { + // ToDo("mute and ignore state change instead? would display video with no sound") + localPlayer.pause() + } ?: localPlayer + } else { + localPlayer + } + } + } + + var player: Player = if (useCastPlayer) castPlayer ?: localPlayer else localPlayer + private set(value) { + val previous = field + field = value + + if (previous != value) { + mediaItem?.let { + value.setMediaItem(it.forPlayer(value), max(progress.value, startingPos)) + value.prepare() + } + } + } + + private var mediaItem: MediaItem? = player.currentMediaItem + set(value) { + val previous = field + field = value + + if (previous != value && value != null) { + player.setMediaItem(value.forPlayer(player), max(progress.value, startingPos)) + player.prepare() + Session.createNew(context, player, value.mediaMetadata) + } + } + + private val _isPlaying = MutableStateFlow(player.isPlaying) + val isPlaying: StateFlow = _isPlaying + + val isCurrentlyPlaying: Boolean + get() = isPlaying.value + + private val _progress = MutableStateFlow(startingPos) + val progress: StateFlow = _progress + + private val _length = MutableStateFlow(max(max(progress.value, player.duration), startingLength)) + val length: StateFlow = _length + + private val _isFinished = MutableStateFlow(player.playbackState == Player.STATE_ENDED) + val isFinished: StateFlow = _isFinished + + private val localPlayerListener = object : Player.Listener { + override fun onRenderedFirstFrame() { + super.onRenderedFirstFrame() + + // Playing works, can switch to casting + castSupported = true + onFirstFrame() + } + } + + private val _showControlsTime = MutableStateFlow(0L) + + @OptIn(ExperimentalCoroutinesApi::class) + val showControls = _showControlsTime.transformLatest { time -> + if (time > 0L) { + emit(true) + + do { + delay(3000) + val newTime = Clock.System.now().toEpochMilliseconds() + if (newTime - time >= 3000) { + _showControlsTime.update { 0L } + } + } while (currentCoroutineContext().isActive) + } else { + emit(false) + } + } + + init { + castPlayer?.addListener(this) + localPlayer.addListener(localPlayerListener) + localPlayer.addListener(this) + + castPlayer?.setSessionAvailabilityListener(this) + + castPlayer?.playWhenReady = true + localPlayer.playWhenReady = true + } + + override fun onCastSessionAvailable() { + requestCasting = true + } + + override fun onCastSessionUnavailable() { + requestCasting = false + } + + override fun onPlayerError(error: PlaybackException) { + super.onPlayerError(error) + + onError(error) + } + + override fun onIsPlayingChanged(isPlaying: Boolean) { + super.onIsPlayingChanged(isPlaying) + + _isPlaying.update { isPlaying } + _isFinished.update { player.playbackState == Player.STATE_ENDED } + onProgressChange(_progress.updateAndGet { player.currentPosition }) + } + + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + super.onTimelineChanged(timeline, reason) + + onProgressChange(_progress.updateAndGet { player.currentPosition }) + _isFinished.update { player.playbackState == Player.STATE_ENDED } + + val newLength = player.duration + if (newLength > 0) { + _length.value = newLength + onLengthChange(newLength) + } + } + + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + + _isFinished.update { player.playbackState == Player.STATE_ENDED } + if (playbackState == Player.STATE_ENDED) { + onFinish() + } + } + + @OptIn(InternalCoilApi::class) + fun play(mediaItem: MediaItem) { + this.mediaItem = mediaItem + } + + fun rewind() { + player.seekBack() + _showControlsTime.update { Clock.System.now().toEpochMilliseconds() } + } + + fun forward() { + player.seekForward() + _showControlsTime.update { Clock.System.now().toEpochMilliseconds() } + } + + fun togglePlay() { + player.togglePlay() + _showControlsTime.update { Clock.System.now().toEpochMilliseconds() } + } + + fun seekTo(position: Long) { + player.seekTo(position) + _showControlsTime.update { Clock.System.now().toEpochMilliseconds() } + } + + suspend fun pollPosition() = withIOContext { + do { + withMainContext { + onProgressChange(_progress.updateAndGet { player.currentPosition }) + _isFinished.update { player.playbackState == Player.STATE_ENDED } + + val newLength = player.duration + if (newLength > 0 && _length.value != newLength) { + onLengthChange(_length.updateAndGet { newLength }) + } + } + delay(100) + } while (currentCoroutineContext().isActive) + } + + fun release() { + localPlayer.removeListener(this) + localPlayer.removeListener(localPlayerListener) + castPlayer?.removeListener(this) + castPlayer?.setSessionAvailabilityListener(null) + + localPlayer.release() + castPlayer?.stop() + castPlayer?.clearMediaItems() + Session.release() + } + + fun showControls() { + _showControlsTime.update { Clock.System.now().toEpochMilliseconds() } + } + + fun hideControls() { + _showControlsTime.update { 0L } + } + + fun showControlsFor5Min() { + _showControlsTime.update { Clock.System.now().plus(5.minutes).toEpochMilliseconds() } + } + + fun toggleControls() { + _showControlsTime.update { + if (it > 0L) { + 0L + } else { + Clock.System.now().toEpochMilliseconds() + } + } + } + + fun dispatchKey(controlsVisible: Boolean, event: KeyEvent): Boolean { + if (event.type != KeyEventType.KeyDown) { + return false + } + + val isDpadKey = event.key.isDpadKey() + var handled = false + + if (isDpadKey && !controlsVisible) { + // Handle the key event by showing the controller. + showControls() + handled = true + } else if (dispatchMediaKey(event)) { + showControls() + handled = true + } else if (isDpadKey) { + // The key event wasn't handled, but we should extend the controller's show timeout. + showControls() + } + return handled + } + + private fun dispatchMediaKey(event: KeyEvent): Boolean { + when { + event.key.isSame(Key.MediaPlay) -> { + player.play() + } + event.key.isSame(Key.MediaPause) -> { + player.pause() + } + event.key.isSame(Key.MediaPlayPause) -> { + player.togglePlay() + } + event.key.isSame(Key.MediaFastForward) -> { + player.seekForward() + } + event.key.isSame(Key.MediaRewind) -> { + player.seekBack() + } + event.key.isSame(Key.MediaNext) -> { + if (length.value > 0L) { + player.seekTo(length.value) + } else { + player.seekToNext() + } + } + event.key.isSame(Key.MediaPrevious) -> { + player.seekToPrevious() + } + else -> { + return false + } + } + return true + } + + private fun Player.togglePlay() { + if (this.isPlaying) { + this.pause() + } else { + this.play() + } + } + + private fun MediaItem.forPlayer(player: Player): MediaItem { + return if (player is CastPlayer) { + this.buildUpon().setMimeType(MimeTypes.VIDEO_UNKNOWN).build() + } else { + this + } + } + + private fun Key.isDpadKey(): Boolean { + return this.isSame(Key.DirectionUp) + || this.isSame(Key.DirectionUpLeft) + || this.isSame(Key.DirectionUpRight) + || this.isSame(Key.DirectionRight) + || this.isSame(Key.DirectionDown) + || this.isSame(Key.DirectionDownLeft) + || this.isSame(Key.DirectionDownRight) + || this.isSame(Key.DirectionLeft) + || this.isSame(Key.DirectionCenter) + } + + private fun Key.isSame(key: Key): Boolean { + return this == key || this.keyCode == key.keyCode || this.nativeKeyCode == key.nativeKeyCode + } + + data object Session { + private var current: MediaSession? = null + private val PseudoRandom = Random((10000..12345).random()) + private val Alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + private const val CHANNEL = "Video" + + fun createNew(context: Context, player: Player, metadata: MediaMetadata) { + release() + + current = MediaSession.Builder( + context, + ForwardingPlayer(player) + ).setId( + NanoIdUtils.randomNanoId( + random = PseudoRandom, + alphabet = Alphabet.toCharArray() + ) + ).build().also { session -> + + NotificationUtil.createNotificationChannel( + context, + CHANNEL, + R.string.session_title, + R.string.session_description, + NotificationUtil.IMPORTANCE_LOW + ) + + val notification = NotificationCompat.Builder(context, CHANNEL) + .setContentTitle(metadata.title) + .setContentText(metadata.subtitle?.ifBlank { null } ?: metadata.albumTitle?.ifBlank { null }) + .setContentInfo(metadata.genre?.ifBlank { null }) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setStyle(MediaStyle(session)) + .build() + + NotificationUtil.setNotification(context, 69, notification) + } + } + + fun release() { + current?.release() + current = null + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt new file mode 100644 index 00000000..072e0ec3 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/TopControls.kt @@ -0,0 +1,206 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Cast +import androidx.compose.material.icons.rounded.Devices +import androidx.compose.material.icons.rounded.Speaker +import androidx.compose.material.icons.rounded.Tv +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.media3.common.util.UnstableApi +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.datlag.burningseries.common.icon +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.cast +import dev.datlag.kast.ConnectionState +import dev.datlag.kast.DeviceType +import dev.datlag.kast.Kast +import dev.datlag.kast.UnselectReason +import dev.datlag.tooling.Platform +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource + +@androidx.annotation.OptIn(UnstableApi::class) +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopControls( + isVisible: Boolean, + mainTitle: String, + subTitle: String?, + playerWrapper: PlayerWrapper, + modifier: Modifier = Modifier, + onBack: () -> Unit, +) { + val isFinished by playerWrapper.isFinished.collectAsStateWithLifecycle() + + AnimatedVisibility( + modifier = modifier.safeDrawingPadding(), + visible = isVisible || isFinished, + enter = slideInVertically() + fadeIn(), + exit = slideOutVertically() + fadeOut() + ) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + Box(modifier = Modifier.matchParentSize().background(Color.Black.copy(alpha = 0.5F))) + TopAppBar( + navigationIcon = { + IconButton( + onClick = onBack + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + }, + title = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) + ) { + Text( + text = mainTitle, + softWrap = true, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + subTitle?.ifBlank { null }?.let { sub -> + Text( + text = sub, + softWrap = true, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.labelMedium + ) + } + } + }, + actions = { + if (!Platform.rememberIsTv()) { + val connectionState by Kast.connectionState.collectAsStateWithLifecycle() + val allAvailableDevices by Kast.allAvailableDevices.collectAsStateWithLifecycle() + val connectDialog = rememberUseCaseState( + onCloseRequest = { + playerWrapper.showControls() + }, + onDismissRequest = { + Kast.Android.passiveDiscovery() + playerWrapper.showControls() + }, + onFinishedRequest = { + playerWrapper.showControls() + } + ) + + OptionDialog( + state = connectDialog, + selection = OptionSelection.Single( + options = allAvailableDevices.map { device -> + Option( + icon = IconSource( + imageVector = when (device.type) { + is DeviceType.TV -> Icons.Rounded.Tv + is DeviceType.SPEAKER -> Icons.Rounded.Speaker + else -> Icons.Rounded.Devices + } + ), + titleText = device.name, + selected = device.isSelected + ) + }, + onSelectOption = { option, _ -> + val device = allAvailableDevices.toList()[option] + + if (device.isSelected) { + Kast.unselect(UnselectReason.disconnected) + Kast.Android.passiveDiscovery() + } else { + Kast.select(device) + } + } + ), + config = OptionConfig( + mode = DisplayMode.LIST + ), + header = Header.Default( + icon = IconSource( + imageVector = Icons.Rounded.Cast + ), + title = stringResource(Res.string.cast) + ) + ) + + when (connectionState) { + is ConnectionState.CONNECTED, is ConnectionState.CONNECTING -> { + IconButton( + onClick = { + Kast.unselect(UnselectReason.disconnected) + } + ) { + Icon( + imageVector = connectionState.icon, + contentDescription = null + ) + } + } + is ConnectionState.DISCONNECTED -> { + IconButton( + onClick = { + Kast.Android.activeDiscovery() + connectDialog.show() + playerWrapper.showControlsFor5Min() + } + ) { + Icon( + imageVector = connectionState.icon, + contentDescription = null + ) + } + } + } + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent, + navigationIconContentColor = Color.White, + titleContentColor = Color.White, + actionIconContentColor = Color.White + ) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt new file mode 100644 index 00000000..8a33dea5 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.android.kt @@ -0,0 +1,260 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.view.Window +import android.view.WindowManager +import androidx.activity.compose.BackHandler +import androidx.annotation.OptIn +import androidx.compose.foundation.background +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.net.toUri +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsControllerCompat +import androidx.media3.cast.CastPlayer +import androidx.media3.cast.SessionAvailabilityListener +import androidx.media3.common.ForwardingPlayer +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.MediaMetadata.MediaType +import androidx.media3.common.MimeTypes +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.hls.DefaultHlsExtractorFactory +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.extractor.DefaultExtractorsFactory +import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory +import androidx.media3.session.MediaSession +import androidx.media3.ui.PlayerView +import com.google.android.gms.cast.framework.CastState +import dev.datlag.burningseries.common.isConnectedOrConnecting +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.burningseries.ui.custom.video.pip.enterPIPMode +import dev.datlag.burningseries.ui.custom.video.pip.isActivityStatePipMode +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.kast.ConnectionState +import dev.datlag.kast.Kast +import dev.datlag.nanoid.NanoIdUtils +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.compose.withMainContext +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import io.github.aakira.napier.Napier +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableMap +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.isActive +import kotlinx.datetime.Clock +import kotlin.random.Random + +@OptIn(UnstableApi::class) +@Composable +actual fun VideoScreen(component: VideoComponent) { + val streamList = remember { component.streams.toImmutableList() } + var streamIndex by remember(streamList) { mutableIntStateOf(0) } + val (url, headers) = remember(streamList, streamIndex) { + streamList[streamIndex] + } + var useLongTimeout by remember { + mutableStateOf(false) + } + val metadata = remember(component.series, component.episode) { + MediaMetadata.Builder() + .setMediaType(MediaMetadata.MEDIA_TYPE_VIDEO) + .setTitle(component.episode.mainTitle) + .setSubtitle(component.episode.subTitle) + .setGenre(component.series.firstGenre) + .setAlbumTitle(component.series.mainTitle) + .setArtworkUri(component.series.coverHref?.toUri()) + .setArtworkData(SchemeTheme.getByteArray(component.series), MediaMetadata.PICTURE_TYPE_FRONT_COVER) + .build() + } + val mediaItem = remember(streamList, url, metadata) { + MediaItem.Builder() + .setUri(url) + .setMediaMetadata( + metadata + ).build() + } + + val context = LocalContext.current + val controller = rememberWindowController() + + val playerWrapper = remember(headers, useLongTimeout) { + PlayerWrapper( + context = context, + castContext = Kast.castContext, + startingPos = component.startingPos, + startingLength = component.startingLength, + headers = headers.toImmutableMap(), + coverData = SchemeTheme.getByteArray(component.series), + longTimeout = useLongTimeout, + onError = { + if (streamList.size - 1 > streamIndex) { + streamIndex++ + } else if (!useLongTimeout) { + useLongTimeout = true + streamIndex = 0 + } + }, + onFirstFrame = { + controller.isSystemBarsVisible = false + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + controller.addWindowFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON and WindowManager.LayoutParams.FLAG_SECURE) + }, + onProgressChange = { + component.progress(it) + }, + onLengthChange = { + component.length(it) + }, + onFinish = { + component.ended() + } + ) + } + + LaunchedEffect(playerWrapper, mediaItem) { + playerWrapper.play(mediaItem) + } + + DisposableEffect(playerWrapper) { + onDispose { + playerWrapper.release() + } + } + + LaunchedEffect(playerWrapper) { + playerWrapper.pollPosition() + } + + val showControls by playerWrapper.showControls.collectAsStateWithLifecycle(false) + var pressedBack by remember { mutableLongStateOf(0L) } + val controlsVisible = showControls && !context.isActivityStatePipMode() + + BackHandler { + val newTime = Clock.System.now().toEpochMilliseconds() + if (pressedBack > 0L && newTime - pressedBack < 3000) { + component.back() + } else { + pressedBack = newTime + } + } + + Scaffold( + topBar = { + TopControls( + isVisible = controlsVisible, + mainTitle = component.episode.mainTitle, + subTitle = component.episode.subTitle ?: component.episode.convertedNumber?.let { "Episode $it" }, + playerWrapper = playerWrapper, + onBack = component::back + ) + }, + bottomBar = { + BottomControls( + isVisible = controlsVisible, + playerWrapper = playerWrapper + ) + } + ) { padding -> + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + val nextEpisodeState by component.nextEpisode.collectAsStateWithLifecycle(EpisodeState.None) + val isPlaying by playerWrapper.isPlaying.collectAsStateWithLifecycle() + + AndroidView( + modifier = Modifier + .fillMaxSize() + .background(Color.Black) + .focusable() + .onKeyEvent { key -> + playerWrapper.dispatchKey(controlsVisible, key) + }, + factory = { viewContext -> + PlayerView(viewContext) + }, + update = { player -> + player.setOnClickListener { + playerWrapper.toggleControls() + } + player.isSoundEffectsEnabled = false + player.useController = false + player.keepScreenOn = true + player.player = playerWrapper.player + } + ) + CenterControls( + modifier = Modifier.padding(padding).fillMaxWidth(), + isVisible = controlsVisible, + isPlaying = isPlaying, + nextState = nextEpisodeState, + playerWrapper = playerWrapper, + onReplayClick = { + playerWrapper.rewind() + }, + onPauseToggle = { + playerWrapper.togglePlay() + }, + onForwardClick = { + playerWrapper.forward() + }, + onNext = component::next + ) + } + } + + DisposableEffect(Unit) { + onDispose { + controller.isSystemBarsVisible = true + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT + controller.clearWindowFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON and WindowManager.LayoutParams.FLAG_SECURE) + } + } +} + +tailrec fun Context.findWindow(): Window? = when (this) { + is Activity -> window + is ContextWrapper -> baseContext.findWindow() + else -> null +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/WindowController.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/WindowController.kt new file mode 100644 index 00000000..8d7dac88 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/WindowController.kt @@ -0,0 +1,78 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import android.view.View +import android.view.Window +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat + +class WindowController( + private val view: View, + private val window: Window? +) { + private val windowInsetsController = window?.let { + WindowCompat.getInsetsController(it, view) + } + + var systemBarsBehavior: Int + get() = windowInsetsController?.systemBarsBehavior ?: 0 + set(value) { + windowInsetsController?.systemBarsBehavior = value + } + + var isStatusBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets( + view + )?.isVisible(WindowInsetsCompat.Type.statusBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.statusBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) + } + } + + var isNavigationBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets( + view + )?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars()) + } + } + + var isSystemBarsVisible: Boolean + get() = isNavigationBarVisible && isStatusBarVisible + set(value) { + isStatusBarVisible = value + isNavigationBarVisible = value + } + + fun addWindowFlags(flags: Int) { + window?.addFlags(flags) + } + + fun clearWindowFlags(flags: Int) { + window?.clearFlags(flags) + } +} + +@Composable +fun rememberWindowController( + view: View = LocalView.current, + window: Window? = (view.parent as? DialogWindowProvider)?.window ?: view.context?.findWindow() ?: LocalContext.current.findWindow() +): WindowController { + return remember(view, window) { WindowController(view, window) } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.android.kt new file mode 100644 index 00000000..05fec773 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.android.kt @@ -0,0 +1,26 @@ +package dev.datlag.burningseries.ui.theme + +import android.os.Build +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +@Composable +actual fun Colors.dynamicDark(): ColorScheme { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + dynamicDarkColorScheme(LocalContext.current) + } else { + getDarkScheme() + } +} + +@Composable +actual fun Colors.dynamicLight(): ColorScheme { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + dynamicLightColorScheme(LocalContext.current) + } else { + getLightScheme() + } +} \ No newline at end of file diff --git a/app/android/src/androidMain/res/drawable/tv_banner_foreground.xml b/composeApp/src/androidMain/res/drawable/ic_banner_foreground.xml similarity index 100% rename from app/android/src/androidMain/res/drawable/tv_banner_foreground.xml rename to composeApp/src/androidMain/res/drawable/ic_banner_foreground.xml diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..6d3bda51 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/android/src/androidMain/res/mipmap-anydpi-v26/tv_banner.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_banner.xml similarity index 51% rename from app/android/src/androidMain/res/mipmap-anydpi-v26/tv_banner.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_banner.xml index 40111ba5..a0a0dece 100644 --- a/app/android/src/androidMain/res/mipmap-anydpi-v26/tv_banner.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_banner.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 97% rename from app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml index 7005cb8e..ef49c991 100644 --- a/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 79% rename from app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml index 7353dbd1..ef49c991 100644 --- a/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..43919037 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..3a60a58c Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..5270c23d Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..69365ee0 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..d8d77f1f Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..d9e43da3 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..1dced041 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..a979deb5 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..7f704c22 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..10e3c324 Binary files /dev/null and b/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/composeApp/src/androidMain/res/values-de/android_specific.xml b/composeApp/src/androidMain/res/values-de/android_specific.xml new file mode 100644 index 00000000..8398b067 --- /dev/null +++ b/composeApp/src/androidMain/res/values-de/android_specific.xml @@ -0,0 +1,6 @@ + + + Burning-Series + Video + Videowiedergabe ändern. + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/values-night/colors.xml b/composeApp/src/androidMain/res/values-night/colors.xml new file mode 100644 index 00000000..6ce3ce47 --- /dev/null +++ b/composeApp/src/androidMain/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + #121212 + #121212 + \ No newline at end of file diff --git a/app/android/src/androidMain/res/values-night/themes.xml b/composeApp/src/androidMain/res/values-night/themes.xml similarity index 100% rename from app/android/src/androidMain/res/values-night/themes.xml rename to composeApp/src/androidMain/res/values-night/themes.xml diff --git a/composeApp/src/androidMain/res/values/android_specific.xml b/composeApp/src/androidMain/res/values/android_specific.xml new file mode 100644 index 00000000..26e7a5aa --- /dev/null +++ b/composeApp/src/androidMain/res/values/android_specific.xml @@ -0,0 +1,6 @@ + + + Burning-Series + Video + Handle video playback. + \ No newline at end of file diff --git a/app/android/src/androidMain/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml similarity index 68% rename from app/android/src/androidMain/res/values/colors.xml rename to composeApp/src/androidMain/res/values/colors.xml index 5ed58835..1be472f9 100644 --- a/app/android/src/androidMain/res/values/colors.xml +++ b/composeApp/src/androidMain/res/values/colors.xml @@ -1,5 +1,5 @@ + #094576 #094576 - #094576 \ No newline at end of file diff --git a/app/android/src/androidMain/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml similarity index 58% rename from app/android/src/androidMain/res/values/themes.xml rename to composeApp/src/androidMain/res/values/themes.xml index 6626c0ce..54fe2e36 100644 --- a/app/android/src/androidMain/res/values/themes.xml +++ b/composeApp/src/androidMain/res/values/themes.xml @@ -7,9 +7,13 @@ \ No newline at end of file diff --git a/app/android/src/androidMain/res/xml/network_security.xml b/composeApp/src/androidMain/res/xml/network_security.xml similarity index 83% rename from app/android/src/androidMain/res/xml/network_security.xml rename to composeApp/src/androidMain/res/xml/network_security.xml index 96e5b6ba..353a117f 100644 --- a/app/android/src/androidMain/res/xml/network_security.xml +++ b/composeApp/src/androidMain/res/xml/network_security.xml @@ -14,10 +14,6 @@ http://bs.to bs.to - https://jsonbase.com - http://jsonbase.com - jsonbase.com - https://googleapis.com http://googleapis.com googleapis.com diff --git a/app/shared/src/commonMain/resources/MR/assets/scrape_hoster_android.js b/composeApp/src/commonMain/composeResources/files/scrape_hoster_android.js similarity index 100% rename from app/shared/src/commonMain/resources/MR/assets/scrape_hoster_android.js rename to composeApp/src/commonMain/composeResources/files/scrape_hoster_android.js diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-bold-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-bold-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-bold-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-bold-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-bold.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-bold.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-bold.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-bold.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-bold-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-extra-bold-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-bold-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-extra-bold-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-bold.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-extra-bold.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-bold.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-extra-bold.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-light-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-extra-light-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-light-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-extra-light-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-light.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-extra-light.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-extra-light.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-extra-light.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-light-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-light-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-light-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-light-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-light.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-light.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-light.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-light.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-medium-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-medium-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-medium-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-medium-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-medium.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-medium.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-medium.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-medium.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-regular-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-regular-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-regular-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-regular-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-regular.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-regular.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-regular.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-regular.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-semi-bold-italic.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-semi-bold-italic.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-semi-bold-italic.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-semi-bold-italic.ttf diff --git a/app/shared/src/commonMain/resources/MR/fonts/Manrope-semi-bold.ttf b/composeApp/src/commonMain/composeResources/font/Manrope-semi-bold.ttf similarity index 100% rename from app/shared/src/commonMain/resources/MR/fonts/Manrope-semi-bold.ttf rename to composeApp/src/commonMain/composeResources/font/Manrope-semi-bold.ttf diff --git a/composeApp/src/commonMain/composeResources/values-de/strings.xml b/composeApp/src/commonMain/composeResources/values-de/strings.xml new file mode 100644 index 00000000..692988b7 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values-de/strings.xml @@ -0,0 +1,74 @@ + + Burning-Series + Start + Sponsern + Favoriten + Standardsprache + Diese Sprache wird bei Serien bevorzugt. + Englisch + Deutsch + Deutsch Untertitel + Englisch Untertitel + Willkommen bei + Los gehts + AniFlow + Synchronisier deinen Anime Fortschritt mit AniList. + Beschreibung + Staffel %1$d + Staffel wählen + Sprache wählen + Cast + Casting wird momentan auf diesem Gerät nicht unterstützt. + Zurück + VLC wird benötigt um Videos auf dem PC anzusehen. + Stelle sicher, dass du nur das offizielle Paket von videolan.org oder deinem Systempaketmanager herunterlädst und installierst. + Herunterladen + Folge aktivieren + Willst du diese Episode jetzt aktivieren? + Aktivieren + Später + Aktivieren auf dem PC wird nicht mehr unterstützt. + Schalte (Google) DoH in deinem Browser an und nutze die Extension stattdessen. + Laden um Folge zu aktivieren + Du kannst jetzt zurückgehen oder weitermachen und mehr aktivieren. + Hoster konnte nicht aktiviert werden, bitte versuche es später nochmal. + Erfolgreich + Fehler + Warnung + Ansehen + Schließen + Suchen + Das Projekt zu Warten ist zeit- und kostenintensiv. Bitte denk darüber nach, einen finanziellen Beitrag zu leisten, um das Projekt am Leben zu halten. + Der Betrag, den du spenden möchtest liegt ganz bei dir, beachte aber, dass nur GitHub Sponsoren oder Patreon (verknüpft mit GitHub) anerkannt werden kann. + Links öffnen + Du bist momentan nicht vor Fake-Seiten geschützt! + Zusätzlich kannst du dann Burning-Series Suchergebnisse in der App sehen. + Aktiviere einfach die Option unterstützte Links zu öffnen und füge diese darunter hinzu. + Aktivieren + Folgen + Serien + Laden + Oh nein + Ein Fehler ist aufgetreten, versuch es später nochmal + Einstellungen synchronisieren + Einstellungen synchonisiert, du kannst das jetzt auf beiden Geräten schließen. + Scan den QR Code mit deinem Smartphone und stelle sicher, dass beide Geräte im selben Netzwerk sind. + Gerät nicht gefunden, stell sicher, dass beide Geräte im selben Netzwerk sind. + Versuche zu Synchronisieren, bitte warten. + Synchronisiere mit %1$s, bitte warten. + Das scheint etwas zu dauern, versuche es vielleicht später nochmal. + GitHub Repository + Entwickelt von DatLag + Einloggen + Ausloggen + GitHub + Polar + Patreon + Installieren + Ansehen + Im Browser öffnen + Eine neue Version ist verfügbar %1$s + Das solltest du dir ansehen! + Synchronisieren aktiviert + Bild in Bild ist hier nicht verfügbar. + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000..5756ff52 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,74 @@ + + Burning-Series + Home + Sponsor + Favorites + Default Language + This language will be preferred when you watch series + English + German + German Subtitle + English Subtitle + Welcome to + Let's Go + AniFlow + Sync your Anime watch progress with AniList. + Description + Season %1$d + Select Season + Select Language + Cast + Casting is not supported on this device yet. + Back + VLC is required to play videos on your PC. + Make sure to only download and install the official package from videolan.org or your system package manager. + Download + Activate Episode + Do you want to activate this episode now? + Activate + Later + Activating on Desktop is no longer supported. + Enable (Google) DoH in your browser and use the extension instead. + Load to activate Episode + You can go back now or continue browsing and activate some more. + Could not activate hoster, please try again later. + Success + Error + Warning + Watch + Close + Search + Maintaining this project is time and money consuming, please consider contributing financially to support it's upkeep. + The amount you want to donate is completely up to you, but keep in mind that only GitHub Sponsors or Patreon (linked with GitHub) can be recognized. + Open Links + You are not protected against fake domains right now! + Additionally you can view Burning-Series search results directly in this app then. + Make sure to check the option to open supported links and add every link below. + Enable + Episodes + Series + Loading + Oh noo + An error occurred, please try again later + Sync Settings + Settings synced, you can close this on both ends now. + Scan the QR Code with your Smartphone and make sure both devices are in the same network. + Device not found, make sure both devices are in the same network. + Trying to sync, please wait. + Syncing to %1$s, please wait. + This seems to take some time, maybe try again later. + GitHub Repository + Developed by DatLag + Login + Logout + GitHub + Polar + Patreon + Install + View + Open in Browser + A new version is available %1$s + You should check it out! + Syncing enabled + Picture in Picture is not available in this screen. + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/App.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/App.kt new file mode 100644 index 00000000..68b2701c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/App.kt @@ -0,0 +1,93 @@ +package dev.datlag.burningseries + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.font.toFontFamily +import dev.chrisbanes.haze.HazeState +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_bold +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_bold_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_extra_bold +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_extra_bold_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_extra_light +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_extra_light_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_light +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_light_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_medium +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_medium_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_regular +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_regular_italic +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_semi_bold +import dev.datlag.burningseries.composeapp.generated.resources.Manrope_semi_bold_italic +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.ui.theme.Colors +import dev.datlag.burningseries.ui.theme.dynamicDark +import dev.datlag.burningseries.ui.theme.dynamicLight +import dev.datlag.tooling.compose.toTypography +import kotlinx.coroutines.flow.MutableStateFlow +import org.jetbrains.compose.resources.Font +import org.kodein.di.DI + +val LocalDarkMode = compositionLocalOf { error("No dark mode state provided") } +val LocalEdgeToEdge = staticCompositionLocalOf { false } +val PictureInPicture = MutableStateFlow(false) +val LocalDI = compositionLocalOf { error("No dependency injection provided") } +val LocalHaze = compositionLocalOf { error("No Haze state provided") } + +@Composable +internal fun App( + di: DI, + systemDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + CompositionLocalProvider( + LocalDarkMode provides systemDarkTheme, + LocalDI provides di + ) { + MaterialTheme( + colorScheme = if (systemDarkTheme) Colors.dynamicDark() else Colors.dynamicLight(), + typography = ManropeFontFamily().toTypography() + ) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground + ) { + content() + } + } + } +} + +@Composable +fun ManropeFontFamily(): FontFamily { + return FontFamily( + Font(Res.font.Manrope_extra_light, FontWeight.ExtraLight), + Font(Res.font.Manrope_extra_light_italic, FontWeight.ExtraLight, FontStyle.Italic), + + Font(Res.font.Manrope_light, FontWeight.Light), + Font(Res.font.Manrope_light_italic, FontWeight.Light, FontStyle.Italic), + + Font(Res.font.Manrope_regular, FontWeight.Normal), + Font(Res.font.Manrope_regular_italic, FontWeight.Normal, FontStyle.Italic), + + Font(Res.font.Manrope_medium, FontWeight.Medium), + Font(Res.font.Manrope_medium_italic, FontWeight.Medium, FontStyle.Italic), + + Font(Res.font.Manrope_semi_bold, FontWeight.SemiBold), + Font(Res.font.Manrope_semi_bold_italic, FontWeight.SemiBold, FontStyle.Italic), + + Font(Res.font.Manrope_bold, FontWeight.Bold), + Font(Res.font.Manrope_bold_italic, FontWeight.Bold, FontStyle.Italic), + + Font(Res.font.Manrope_extra_bold, FontWeight.ExtraBold), + Font(Res.font.Manrope_extra_bold_italic, FontWeight.ExtraBold, FontStyle.Italic), + ) +} diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendCompose.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendCompose.kt new file mode 100644 index 00000000..de254a29 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendCompose.kt @@ -0,0 +1,196 @@ +package dev.datlag.burningseries.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridItemScope +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue +import androidx.compose.material3.SwitchColors +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +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.draw.drawWithContent +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.max +import com.kmpalette.DominantColorState + +@Composable +operator fun PaddingValues.plus(other: PaddingValues): PaddingValues { + val direction = LocalLayoutDirection.current + + return PaddingValues( + start = this.calculateStartPadding(direction) + other.calculateStartPadding(direction), + top = this.calculateTopPadding() + other.calculateTopPadding(), + end = this.calculateEndPadding(direction) + other.calculateEndPadding(direction), + bottom = this.calculateBottomPadding() + other.calculateBottomPadding() + ) +} + +@Composable +operator fun PaddingValues.plus(all: Dp): PaddingValues { + val direction = LocalLayoutDirection.current + val other = PaddingValues(all) + + return PaddingValues( + start = this.calculateStartPadding(direction) + other.calculateStartPadding(direction), + top = this.calculateTopPadding() + other.calculateTopPadding(), + end = this.calculateEndPadding(direction) + other.calculateEndPadding(direction), + bottom = this.calculateBottomPadding() + other.calculateBottomPadding() + ) +} + +@Composable +fun PaddingValues.merge(other: PaddingValues): PaddingValues { + val direction = LocalLayoutDirection.current + + return PaddingValues( + start = max(this.calculateStartPadding(direction), other.calculateStartPadding(direction)), + top = max(this.calculateTopPadding(), other.calculateTopPadding()), + end = max(this.calculateEndPadding(direction), other.calculateEndPadding(direction)), + bottom = max(this.calculateBottomPadding(), other.calculateBottomPadding()) + ) +} + +@Composable +fun PaddingValues.merge(all: Dp): PaddingValues { + val direction = LocalLayoutDirection.current + val other = PaddingValues(all) + + return PaddingValues( + start = max(this.calculateStartPadding(direction), other.calculateStartPadding(direction)), + top = max(this.calculateTopPadding(), other.calculateTopPadding()), + end = max(this.calculateEndPadding(direction), other.calculateEndPadding(direction)), + bottom = max(this.calculateBottomPadding(), other.calculateBottomPadding()) + ) +} + +val DominantColorState?.primary + @Composable + get() = this?.color ?: MaterialTheme.colorScheme.primary + +val DominantColorState?.onPrimary + @Composable + get() = this?.onColor ?: MaterialTheme.colorScheme.onPrimary + +val Color.plainOnColor: Color + get() = if (this.luminance() > 0.5F) { + Color.Black + } else { + Color.White + } + +fun Modifier.bottomShadowBrush(color: Color, alpha: Float = 1F): Modifier { + val maxAlpha = kotlin.math.min(alpha, 1F) + + return this.background( + brush = Brush.verticalGradient( + 0.0f to Color.Transparent, + 0.1f to color.copy(alpha = 0.35f * maxAlpha), + 0.3f to color.copy(alpha = 0.55f * maxAlpha), + 0.5f to color.copy(alpha = 0.75f * maxAlpha), + 0.7f to color.copy(alpha = 0.95f * maxAlpha), + 0.9f to color.copy(alpha = 1f * maxAlpha) + ) + ) +} + +fun LazyGridScope.fullRow( + content: @Composable LazyGridItemScope.() -> Unit +) { + item(span = { GridItemSpan(this.maxLineSpan) }, content = content) +} + +/** + * Checks if the modal is currently expanded or a swipe action is in progress to be expanded. + */ +@OptIn(ExperimentalMaterial3Api::class) +fun SheetState.isFullyExpandedOrTargeted(forceFullExpand: Boolean = false): Boolean { + val checkState = if (this.hasExpandedState) { + if (!this.hasPartiallyExpandedState) { + return false + } + SheetValue.Expanded + } else { + if (forceFullExpand) { + return false + } + SheetValue.PartiallyExpanded + } + + return this.targetValue == checkState +} + +@Composable +fun SwitchDefaults.alwaysEnabledColors(): SwitchColors { + val defaultColors = SwitchDefaults.colors() + + return SwitchColors( + checkedThumbColor = defaultColors.checkedThumbColor, + checkedTrackColor = defaultColors.checkedTrackColor, + checkedBorderColor = defaultColors.checkedBorderColor, + checkedIconColor = defaultColors.checkedIconColor, + uncheckedThumbColor = defaultColors.uncheckedThumbColor, + uncheckedTrackColor = defaultColors.uncheckedTrackColor, + uncheckedBorderColor = defaultColors.uncheckedBorderColor, + uncheckedIconColor = defaultColors.uncheckedIconColor, + disabledCheckedThumbColor = defaultColors.checkedThumbColor, + disabledCheckedTrackColor = defaultColors.checkedTrackColor, + disabledCheckedBorderColor = defaultColors.checkedBorderColor, + disabledCheckedIconColor = defaultColors.checkedIconColor, + disabledUncheckedThumbColor = defaultColors.uncheckedThumbColor, + disabledUncheckedTrackColor = defaultColors.uncheckedTrackColor, + disabledUncheckedBorderColor = defaultColors.uncheckedBorderColor, + disabledUncheckedIconColor = defaultColors.uncheckedIconColor, + ) +} + +@Composable +fun LazyListState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { + mutableStateOf(firstVisibleItemIndex) + } + var previousScrollOffset by remember(this) { + mutableStateOf(firstVisibleItemScrollOffset) + } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +} + +val LazyListState.canScroll: Boolean + get() = this.canScrollForward || this.canScrollBackward + +@Composable +fun LazyListState.scrollUpVisible(): Boolean { + return if (canScroll) { + isScrollingUp() && canScrollForward + } else { + true + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendDI.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendDI.kt new file mode 100644 index 00000000..73855ddf --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendDI.kt @@ -0,0 +1,21 @@ +package dev.datlag.burningseries.common + +import dev.datlag.burningseries.firebase.FirebaseFactory +import org.kodein.di.DIAware +import org.kodein.di.DirectDI +import org.kodein.di.instanceOrNull + +fun DIAware.nullableFirebaseInstance(): FirebaseFactory? { + val instance by this.instanceOrNull() + return when (val state = instance) { + is FirebaseFactory.Empty -> null + else -> state + } +} + +fun DirectDI.nullableFirebaseInstance(): FirebaseFactory? { + return when (val instance = instanceOrNull()) { + is FirebaseFactory.Empty -> null + else -> instance + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendFirebase.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendFirebase.kt new file mode 100644 index 00000000..a671b83c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendFirebase.kt @@ -0,0 +1,9 @@ +package dev.datlag.burningseries.common + +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.model.common.name +import dev.datlag.burningseries.ui.navigation.Component + +fun FirebaseFactory.Crashlytics.screen(value: Component) { + this.customKey("Screen", value::class.name) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendInfo.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendInfo.kt new file mode 100644 index 00000000..954a5626 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendInfo.kt @@ -0,0 +1,154 @@ +package dev.datlag.burningseries.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.DateRange +import androidx.compose.material.icons.rounded.Draw +import androidx.compose.material.icons.rounded.SportsKabaddi +import androidx.compose.material.icons.rounded.SupervisorAccount +import androidx.compose.material.icons.rounded.VideoCameraFront +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +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.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.utils.AndroidWindowSizeFix +import dev.datlag.burningseries.model.Series +import io.github.aakira.napier.Napier + +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +fun Series.Info.display( + modifier: Modifier = Modifier +) { + AndroidWindowSizeFix { + val mapped = remember(header) { InfoMapping.from(header) } + val useIcon = when (calculateWindowSizeClass().widthSizeClass) { + WindowWidthSizeClass.Compact -> true + else -> false + } + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (useIcon && mapped != null) { + Icon( + imageVector = mapped.imageVector, + contentDescription = header + ) + } else { + Text( + text = "${header}:", + fontWeight = FontWeight.SemiBold, + maxLines = 1 + ) + } + Text( + text = data, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + softWrap = true + ) + } + } +} + +private sealed interface InfoMapping { + val label: String + val alternativeLabel: String + get() = label + + val imageVector: ImageVector + + fun isInfo(value: String): Boolean { + return when { + value.equals(label, ignoreCase = true) -> true + value.equals(alternativeLabel, ignoreCase = true) -> true + else -> false + } + } + + data object Year : InfoMapping { + override val label: String + get() = "Produktionsjahre" + + override val alternativeLabel: String + get() = "Produktionsjahr" + + override val imageVector: ImageVector + get() = Icons.Rounded.DateRange + + } + + data object Actor : InfoMapping { + override val label: String + get() = "Hauptdarsteller" + + override val alternativeLabel: String + get() = "Darsteller" + + override val imageVector: ImageVector + get() = Icons.Rounded.SportsKabaddi + } + + data object Producer : InfoMapping { + override val label: String + get() = "Produzenten" + + override val alternativeLabel: String + get() = "Produzent" + + override val imageVector: ImageVector + get() = Icons.Rounded.SupervisorAccount + } + + data object Director : InfoMapping { + override val label: String + get() = "Regisseure" + + override val alternativeLabel: String + get() = "Regisseur" + + override val imageVector: ImageVector + get() = Icons.Rounded.VideoCameraFront + } + + data object Author : InfoMapping { + override val label: String + get() = "Autoren" + + override val alternativeLabel: String + get() = "Autor" + + override val imageVector: ImageVector + get() = Icons.Rounded.Draw + } + + companion object { + fun from(value: String): InfoMapping? { + return when { + Year.isInfo(value) -> Year + Actor.isInfo(value) -> Actor + Producer.isInfo(value) -> Producer + Director.isInfo(value) -> Director + Author.isInfo(value) -> Author + else -> null + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendKast.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendKast.kt new file mode 100644 index 00000000..e6be08c4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendKast.kt @@ -0,0 +1,20 @@ +package dev.datlag.burningseries.common + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Cast +import androidx.compose.material.icons.rounded.CastConnected +import androidx.compose.ui.graphics.vector.ImageVector +import dev.datlag.kast.ConnectionState + +val ConnectionState.isConnectedOrConnecting: Boolean + get() = when (this) { + is ConnectionState.CONNECTED, is ConnectionState.CONNECTING -> true + else -> false + } + +val ConnectionState.icon: ImageVector + get() = if (isConnectedOrConnecting) { + Icons.Rounded.CastConnected + } else { + Icons.Rounded.Cast + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendLanguage.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendLanguage.kt new file mode 100644 index 00000000..e338ac8d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendLanguage.kt @@ -0,0 +1,36 @@ +package dev.datlag.burningseries.common + +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.english +import dev.datlag.burningseries.composeapp.generated.resources.english_subtitle +import dev.datlag.burningseries.composeapp.generated.resources.select_default_language +import dev.datlag.burningseries.composeapp.generated.resources.german +import dev.datlag.burningseries.composeapp.generated.resources.german_subtitle +import dev.datlag.burningseries.model.Home +import dev.datlag.burningseries.other.CountryImage +import dev.datlag.burningseries.settings.model.Language +import dev.icerock.moko.resources.ImageResource +import kotlinx.collections.immutable.ImmutableSet +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.StringResource + +val Language?.title: StringResource + get() = when (this) { + null -> Res.string.select_default_language + is Language.English -> Res.string.english + is Language.German.Default -> Res.string.german + is Language.German.Subtitle -> Res.string.german_subtitle + is Language.JapaneseSubtitle -> Res.string.english_subtitle + } + +val Home.Episode.Flag.languageByCode: StringResource? + get() = when { + this.bestCountryCode.equals(Language.English.code, ignoreCase = true) -> Res.string.english + this.bestCountryCode.equals(Language.German.Default.code, ignoreCase = true) -> Res.string.german + this.bestCountryCode.equals(Language.German.Subtitle.code, ignoreCase = true) -> Res.string.german_subtitle + this.bestCountryCode.equals(Language.JapaneseSubtitle.code, ignoreCase = true) -> Res.string.english_subtitle + else -> null + } + +val Language?.flags: ImmutableSet + get() = CountryImage.getByFlag(this?.code) \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendNumber.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendNumber.kt similarity index 89% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendNumber.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendNumber.kt index ea9a0155..63312427 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/common/ExtendNumber.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/ExtendNumber.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared.common +package dev.datlag.burningseries.common fun Long.toDuration(): String { val duration = this / 1000 diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.kt new file mode 100644 index 00000000..ad10990b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.kt @@ -0,0 +1,20 @@ +package dev.datlag.burningseries.common + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import com.vanniktech.blurhash.BlurHash +import dev.datlag.tooling.Platform + +expect fun BlurHash.decode( + hash: String?, + width: Int, + height: Int +): ImageBitmap? + +@Composable +expect fun Modifier.drawProgress(color: Color, progress: Float): Modifier + +@Composable +expect fun Platform.rememberIsTv(): Boolean \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/DatabaseModule.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/DatabaseModule.kt new file mode 100644 index 00000000..6cfb139f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/DatabaseModule.kt @@ -0,0 +1,53 @@ +package dev.datlag.burningseries.module + +import app.cash.sqldelight.ColumnAdapter +import app.cash.sqldelight.db.SqlDriver +import org.kodein.di.DI +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import dev.datlag.burningseries.database.DriverFactory +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.Episode +import dev.datlag.burningseries.database.InstantAdapter +import dev.datlag.burningseries.database.IntAdapter +import dev.datlag.burningseries.database.Series + +data object DatabaseModule { + + const val NAME = "DatabaseModule" + + val di = DI.Module(NAME) { + import(PlatformModule.di) + + bindSingleton { + instance().createBurningSeriesDriver() + } + bindSingleton { + BurningSeries( + driver = instance(), + SeriesAdapter = Series.Adapter( + seasonAdapter = IntAdapter, + seasonsAdapter = object : ColumnAdapter, String> { + override fun decode(databaseValue: String): List { + return if (databaseValue.isBlank()) { + emptyList() + } else { + databaseValue.split(',').mapNotNull { + it.trim().toIntOrNull() + } + } + } + + override fun encode(value: List): String { + return value.joinToString(separator = ",") + } + } + ), + EpisodeAdapter = Episode.Adapter( + numberAdapter = IntAdapter, + updatedAtAdapter = InstantAdapter + ) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/NetworkModule.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/NetworkModule.kt new file mode 100644 index 00000000..069a8aa0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/NetworkModule.kt @@ -0,0 +1,150 @@ +package dev.datlag.burningseries.module + +import coil3.ImageLoader +import coil3.PlatformContext +import coil3.disk.DiskCache +import coil3.memory.MemoryCache +import coil3.network.ktor.KtorNetworkFetcherFactory +import coil3.request.crossfade +import coil3.svg.SvgDecoder +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.http.HttpRequest +import com.apollographql.apollo3.api.http.HttpResponse +import com.apollographql.apollo3.network.http.HttpInterceptor +import com.apollographql.apollo3.network.http.HttpInterceptorChain +import de.jensklingenberg.ktorfit.ktorfit +import dev.datlag.burningseries.BuildKonfig +import dev.datlag.burningseries.Sekret +import dev.datlag.burningseries.common.nullableFirebaseInstance +import dev.datlag.burningseries.github.GitHub +import dev.datlag.burningseries.network.EpisodeStateMachine +import dev.datlag.burningseries.network.HomeStateMachine +import dev.datlag.burningseries.network.SaveStateMachine +import dev.datlag.burningseries.network.SearchStateMachine +import dev.datlag.burningseries.network.SeriesStateMachine +import dev.datlag.burningseries.other.SyncHelper +import dev.datlag.burningseries.other.UserHelper +import dev.datlag.tooling.compose.ioDispatcher +import io.ktor.client.HttpClient +import kotlinx.serialization.json.Json +import okio.FileSystem +import org.kodein.di.DI +import org.kodein.di.bindProvider +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import org.kodein.di.instanceOrNull +import org.publicvalue.multiplatform.oidc.ExperimentalOpenIdConnect +import org.publicvalue.multiplatform.oidc.OpenIdConnectClient +import org.publicvalue.multiplatform.oidc.appsupport.CodeAuthFlowFactory +import org.publicvalue.multiplatform.oidc.flows.CodeAuthFlow + +data object NetworkModule { + + const val NAME = "NetworkModule" + + @OptIn(ExperimentalOpenIdConnect::class) + val di = DI.Module(NAME) { + import(DatabaseModule.di) + + bindSingleton { + Json { + ignoreUnknownKeys = true + isLenient = true + } + } + bindSingleton { + ImageLoader.Builder(instance()) + .components { + add(KtorNetworkFetcherFactory(instance())) + add(SvgDecoder.Factory()) + } + .memoryCache { + MemoryCache.Builder() + .maxSizePercent(instance()) + .build() + } + .diskCache { + DiskCache.Builder() + .directory(FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "image_cache") + .maxSizeBytes(512L * 1024 * 1024) // 512 MB + .build() + } + .crossfade(true) + .extendImageLoader() + .build() + } + bindProvider { + HomeStateMachine( + client = instance(), + crashlytics = nullableFirebaseInstance()?.crashlytics + ) + } + bindProvider { + SearchStateMachine( + client = instance(), + crashlytics = nullableFirebaseInstance()?.crashlytics + ) + } + bindProvider { + SeriesStateMachine( + client = instance(), + crashlytics = nullableFirebaseInstance()?.crashlytics + ) + } + bindProvider { + EpisodeStateMachine( + client = instanceOrNull("STREAM_CLIENT") ?: instance(), + firebaseAuth = nullableFirebaseInstance()?.auth, + fireStore = nullableFirebaseInstance()?.store, + crashlytics = nullableFirebaseInstance()?.crashlytics + ) + } + bindProvider { + SaveStateMachine( + client = instance(), + streamClient = instanceOrNull("STREAM_CLIENT"), + firebaseAuth = nullableFirebaseInstance()?.auth, + fireStore = nullableFirebaseInstance()?.store, + crashlytics = nullableFirebaseInstance()?.crashlytics + ) + } + bindSingleton { + val ktorfit = ktorfit { + httpClient(instance()) + baseUrl("https://api.github.com/") + } + ktorfit.create() + } + bindSingleton { + OpenIdConnectClient { + endpoints { + authorizationEndpoint = "https://github.com/login/oauth/authorize" + tokenEndpoint = "https://github.com/login/oauth/access_token" + } + + clientId = Sekret.githubClientId(BuildKonfig.packageName) + clientSecret = Sekret.githubClientSecret(BuildKonfig.packageName) + scope = "read:user" + } + } + bindProvider { + instance().createAuthFlow(instance()) + } + bindSingleton { + UserHelper( + github = instance(), + appVersion = instanceOrNull("APP_VERSION"), + oidcClient = instance(), + tokenStore = instance(), + appSettings = instance(), + database = instance() + ) + } + bindSingleton { + SyncHelper( + appSettings = instance(), + userSettings = instance() + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/PlatformModule.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/PlatformModule.kt new file mode 100644 index 00000000..eb4bad8c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/module/PlatformModule.kt @@ -0,0 +1,10 @@ +package dev.datlag.burningseries.module + +import coil3.ImageLoader +import org.kodein.di.DI + +expect object PlatformModule { + val di: DI.Module +} + +expect fun ImageLoader.Builder.extendImageLoader(): ImageLoader.Builder \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/AniFlow.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/AniFlow.kt new file mode 100644 index 00000000..3ad14eb9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/AniFlow.kt @@ -0,0 +1,20 @@ +package dev.datlag.burningseries.other + +import androidx.compose.runtime.Composable +import dev.icerock.moko.resources.ImageResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.aniflow +import dev.datlag.burningseries.composeapp.generated.resources.aniflow_subtitle +import org.jetbrains.compose.resources.StringResource + +data object AniFlow { + val icon: ImageResource = MokoRes.images.AniFlow + val title: StringResource = Res.string.aniflow + val subTitle: StringResource = Res.string.aniflow_subtitle + const val packageName = "dev.datlag.aniflow" + const val googlePlay = "https://play.google.com/store/apps/details?id=$packageName" +} + +@Composable +expect fun AniFlow.isInstalled(): Boolean \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/Constants.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/Constants.kt new file mode 100644 index 00000000..129702d6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/Constants.kt @@ -0,0 +1,21 @@ +package dev.datlag.burningseries.other + +data object Constants { + const val VLC_WEBSITE = "https://www.videolan.org/" + + const val GITHUB_OWNER_NAME = "DatL4g" + const val GITHUB_REPO_NAME = "Burning-Series" + + const val GITHUB_OWNER = "https://github.com/$GITHUB_OWNER_NAME" + const val GITHUB_REPO = "$GITHUB_OWNER/$GITHUB_REPO_NAME" + const val GITHUB_RELEASE = "$GITHUB_REPO/releases/latest" + + const val SYNCING_DOMAIN = "burningseries.datlag" + const val SYNCING_URL = "https://burningseries.datlag/sync/" + + data object Sponsor { + const val GITHUB = "https://github.com/sponsors/DatL4g" + const val POLAR = "https://polar.sh/DatL4g" + const val PATREON = "https://patreon.com/datlag" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/CountryImage.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/CountryImage.kt new file mode 100644 index 00000000..ff9defb2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/CountryImage.kt @@ -0,0 +1,142 @@ +package dev.datlag.burningseries.other + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.foundation.layout.size +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.foundation.border +import androidx.compose.material3.LocalContentColor +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import dev.datlag.tooling.compose.ifTrue +import dev.icerock.moko.resources.ImageResource +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import dev.icerock.moko.resources.compose.painterResource +import dev.datlag.burningseries.MokoRes + +data object CountryImage { + + fun getByCode(code: String): ImageResource { + val bestCode = code.split("[-_]".toRegex()).firstOrNull() ?: code + + return when { + bestCode.equals( + "EN", + ignoreCase = true + ) || bestCode.equals( + "US", + ignoreCase = true + ) -> MokoRes.images.US + + bestCode.equals("DE", ignoreCase = true) -> MokoRes.images.DE + bestCode.equals("JP", ignoreCase = true) -> MokoRes.images.JP + else -> MokoRes.images.COUNTRY_UNKNOWN + } + } + + fun getByFlag(code: String?): ImmutableSet { + return when { + code == null -> persistentSetOf( + MokoRes.images.COUNTRY_UNKNOWN + ) + code.equals("DES", ignoreCase = true) -> persistentSetOf( + MokoRes.images.US, + MokoRes.images.DE + ) + code.equals("JPS", ignoreCase = true) -> persistentSetOf( + MokoRes.images.JP, + MokoRes.images.US + ) + else -> persistentSetOf(getByCode(code)) + } + } + + @Composable + fun showFlags( + code: String?, + modifier: Modifier = Modifier, + description: String? = null, + iconSize: Dp? = null, + showBorder: Boolean = false, + shape: Shape = MaterialTheme.shapes.extraSmall + ) { + code?.let { + val flags = getByFlag(code) + + if (flags.isNotEmpty()) { + showFlags( + collection = flags, + description = description, + iconSize = iconSize, + showBorder = showBorder, + shape = shape + ) + } + } + } + + @Composable + fun showFlags( + collection: ImmutableCollection, + modifier: Modifier = Modifier, + description: String? = null, + iconSize: Dp? = null, + showBorder: Boolean = false, + shape: Shape = MaterialTheme.shapes.extraSmall + ) { + var iconWidth by remember { mutableFloatStateOf(iconSize?.value ?: 24.dp.value) } + var iconHeight by remember { mutableFloatStateOf(iconSize?.value ?: 24.dp.value) } + + Box( + modifier = modifier.ifTrue(iconSize == null) { + this.onSizeChanged { + val thirdWidth = it.width.toFloat() - (it.width.toFloat() / 2F) + val thirdHeight = it.height.toFloat() - (it.height.toFloat() / 2F) + iconWidth = thirdWidth + iconHeight = thirdHeight + } + }.ifTrue(collection.size >= 2) { + size(width = (iconWidth * 1.5).dp, height = (iconHeight * 1.5).dp) + }, + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(collection.last()), + contentDescription = description, + modifier = Modifier + .size(width = iconWidth.dp, height = iconHeight.dp) + .clip(shape) + .ifTrue(showBorder) { + border(1.dp, LocalContentColor.current, shape) + } + .ifTrue(collection.size >= 2) { this.align(Alignment.TopStart).alpha(0.75F) } + ) + if (collection.size >= 2) { + Image( + painter = painterResource(collection.first()), + contentDescription = description, + modifier = Modifier + .size(width = iconWidth.dp, height = iconHeight.dp) + .clip(shape) + .ifTrue(showBorder) { + border(1.dp, LocalContentColor.current, shape) + } + .align(Alignment.BottomEnd) + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/StateSaver.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/StateSaver.kt new file mode 100644 index 00000000..43b3ff46 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/StateSaver.kt @@ -0,0 +1,5 @@ +package dev.datlag.burningseries.other + +data object StateSaver { + var sekretLibraryLoaded: Boolean = false +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/SyncHelper.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/SyncHelper.kt new file mode 100644 index 00000000..1e97d5d9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/SyncHelper.kt @@ -0,0 +1,54 @@ +package dev.datlag.burningseries.other + +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.AppSettings +import dev.datlag.burningseries.settings.model.UserSettings +import dev.datlag.tooling.async.suspendCatching +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.ProtoNumber + +@OptIn(ExperimentalSerializationApi::class) +class SyncHelper( + private val appSettings: Settings.PlatformAppSettings, + private val userSettings: Settings.PlatformUserSettings +) { + + suspend fun encodeSettingsToByteArray(): ByteArray { + val app = appSettings.all.firstOrNull() ?: return ByteArray(0) + val user = userSettings.all.firstOrNull() ?: return ByteArray(0) + + val data = SyncSettings(app, user) + return protoBuf.encodeToByteArray(data) + } + + suspend fun updateSettingsFromByteArray(data: ByteArray): Boolean { + if (data.isEmpty()) { + return false + } + + val syncSettings = suspendCatching { + protoBuf.decodeFromByteArray(data) + }.getOrNull() ?: return false + + appSettings.updateAll(syncSettings.appSettings) + userSettings.updateAll(syncSettings.userSettings) + return true + } + + @Serializable + data class SyncSettings( + @ProtoNumber(1) val appSettings: AppSettings, + @ProtoNumber(2) val userSettings: UserSettings + ) + + companion object { + private val protoBuf = ProtoBuf { + encodeDefaults = true + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/UserHelper.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/UserHelper.kt new file mode 100644 index 00000000..b5039a03 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/other/UserHelper.kt @@ -0,0 +1,188 @@ +package dev.datlag.burningseries.other + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.http.HttpRequest +import com.apollographql.apollo3.api.http.HttpResponse +import com.apollographql.apollo3.network.http.HttpInterceptor +import com.apollographql.apollo3.network.http.HttpInterceptorChain +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.common.countWatchedEpisodes +import dev.datlag.burningseries.github.GitHub +import dev.datlag.burningseries.github.UserAndReleaseQuery +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.settings.Settings +import dev.datlag.tooling.async.suspendCatching +import dev.datlag.tooling.compose.ioDispatcher +import io.github.aakira.napier.Napier +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withTimeout +import kotlinx.datetime.Clock +import org.publicvalue.multiplatform.oidc.ExperimentalOpenIdConnect +import org.publicvalue.multiplatform.oidc.OpenIdConnectClient +import org.publicvalue.multiplatform.oidc.flows.CodeAuthFlow +import org.publicvalue.multiplatform.oidc.tokenstore.TokenRefreshHandler +import org.publicvalue.multiplatform.oidc.tokenstore.TokenStore +import org.publicvalue.multiplatform.oidc.tokenstore.saveTokens +import org.publicvalue.multiplatform.oidc.types.remote.AccessTokenResponse + +@OptIn(ExperimentalOpenIdConnect::class) +class UserHelper( + private val github: GitHub, + private val appVersion: String?, + private val oidcClient: OpenIdConnectClient, + private val tokenStore: TokenStore, + private val appSettings: Settings.PlatformAppSettings, + private val database: BurningSeries +) { + + private val client = ApolloClient.Builder() + .dispatcher(ioDispatcher()) + .serverUrl("https://api.github.com/graphql") + .addHttpInterceptor(object : HttpInterceptor { + override suspend fun intercept( + request: HttpRequest, + chain: HttpInterceptorChain + ): HttpResponse { + val req = request.newBuilder().apply { + val token = getAccessToken() + + token?.let { + addHeader("Authorization", "Bearer $it") + } + }.build() + + return chain.proceed(req) + } + }) + .build() + + private val refreshHandler = TokenRefreshHandler(tokenStore) + + private val query = Query() + private val tokenDate = MutableStateFlow(Clock.System.now().epochSeconds) + + @OptIn(ExperimentalCoroutinesApi::class) + private val userAndRelease = tokenDate.transformLatest { + return@transformLatest emitAll( + client.query(query.toGraphQL()).toFlow() + ) + }.map { + it.data?.let(::UserAndRelease) + }.map { + it ?: suspendCatching { + github.getLatestRelease(query.owner, query.repo) + }.getOrNull()?.let(::UserAndRelease) + }.map { data -> + if (data?.user != null) { + _user.update { data.user } + } + val latestRelease = data?.release?.asUpdateOrNull(appVersion) + if (latestRelease != null) { + _release.update { latestRelease } + } + data + }.flowOn( + context = ioDispatcher() + ).stateIn( + scope = GlobalScope, + started = SharingStarted.Eagerly, + initialValue = null + ) + + private val _user: MutableStateFlow = MutableStateFlow(null) + val user: StateFlow = _user + + private val _release: MutableStateFlow = MutableStateFlow(null) + val release: StateFlow = _release + + val isSponsoring: Boolean + get() = user.value?.isSponsoring == true + + suspend fun login(authFlow: CodeAuthFlow): AccessTokenResponse? { + val access = suspendCatching { + authFlow.getAccessToken() + }.getOrNull() + + access?.let { + tokenStore.saveTokens(it) + tokenDate.update { Clock.System.now().epochSeconds } + } + + return access + } + + suspend fun logout() { + tokenStore.saveTokens( + accessToken = "", + refreshToken = null, + idToken = null + ) + _user.update { null } + tokenDate.update { Clock.System.now().epochSeconds } + } + + suspend fun getAccessWithAuthFlowToken(authFlow: CodeAuthFlow): String? { + val oldToken = tokenStore.getAccessToken() + + if (oldToken.isNullOrBlank()) { + return login(authFlow)?.access_token?.ifBlank { null } ?: oldToken + } + + val token = suspendCatching { + withTimeout(2000) { + refreshHandler.refreshAndSaveToken(oidcClient, oldToken) + } + }.getOrNull() + + if (token == null) { + return login(authFlow)?.access_token?.ifBlank { null } ?: oldToken + } + + return token.accessToken.ifBlank { null } + } + + suspend fun getAccessToken(): String? { + val oldToken = tokenStore.getAccessToken() ?: return null + + val token = suspendCatching(catchTimeout = true) { + withTimeout(2000) { + refreshHandler.refreshAndSaveToken(oidcClient, oldToken) + } + }.getOrNull() + + return token?.accessToken?.ifBlank { null } ?: oldToken + } + + suspend fun requiresSponsoring(): Boolean { + if ((appSettings.startCounter.firstOrNull() ?: 0) >= 15) { + return true + } + + val watched = database.countWatchedEpisodes() + return watched >= 30 + } + + private data class Query( + val owner: String = Constants.GITHUB_OWNER_NAME, + val repo: String = Constants.GITHUB_REPO_NAME + ) { + fun toGraphQL() = UserAndReleaseQuery( + owner = owner, + repo = repo + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/ErrorContent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/ErrorContent.kt new file mode 100644 index 00000000..2da2859c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/ErrorContent.kt @@ -0,0 +1,82 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.LocalDarkMode +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.StringResource +import org.jetbrains.compose.resources.stringResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.error_try_again +import dev.datlag.burningseries.composeapp.generated.resources.oh_noo + +@Composable +fun ErrorContent( + modifier: Modifier = Modifier, + horizontal: Boolean = false, + painter: ImageResource = if (LocalDarkMode.current) MokoRes.images.donut_dark else MokoRes.images.donut_light, + title: StringResource = Res.string.oh_noo, + text: StringResource = Res.string.error_try_again +) { + if (horizontal) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) + ) { + Image( + modifier = Modifier.weight(1F), + painter = painterResource(painter), + contentDescription = null + ) + Column( + modifier = Modifier.weight(1.5F), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.Start + ) { + Text( + text = stringResource(title), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + Text(text = stringResource(text)) + } + } + } else { + Column( + modifier = modifier.padding(horizontal = 16.dp, vertical = 32.dp), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(painter), + contentDescription = null + ) + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(title), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(text), + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/MaterialSymbols.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/MaterialSymbols.kt new file mode 100644 index 00000000..397be30f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/MaterialSymbols.kt @@ -0,0 +1,124 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.material.icons.materialIcon +import androidx.compose.material.icons.materialPath +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +data object MaterialSymbols { + + data object Rounded { + private var _home: ImageVector? = null + + val Home: ImageVector + get() { + return _home ?: ImageVector.Builder( + name = "home", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 40f, + viewportHeight = 40f + ).apply { + path( + fill = SolidColor(Color.Black), + fillAlpha = 1f, + stroke = null, + strokeAlpha = 1f, + strokeLineWidth = 1.0f, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Miter, + strokeLineMiter = 1f, + pathFillType = PathFillType.NonZero + ) { + moveTo(9.542f, 32.125f) + horizontalLineToRelative(5.75f) + verticalLineToRelative(-10.25f) + horizontalLineToRelative(9.416f) + verticalLineToRelative(10.25f) + horizontalLineToRelative(5.75f) + verticalLineTo(16.417f) + lineTo(20f, 8.583f) + lineTo(9.542f, 16.417f) + close() + moveToRelative(0f, 2.625f) + quadToRelative(-1.084f, 0f, -1.855f, -0.771f) + quadToRelative(-0.77f, -0.771f, -0.77f, -1.854f) + verticalLineTo(16.417f) + quadToRelative(0f, -0.625f, 0.271f, -1.188f) + quadToRelative(0.27f, -0.562f, 0.77f, -0.937f) + lineToRelative(10.459f, -7.834f) + quadToRelative(0.375f, -0.25f, 0.771f, -0.375f) + quadToRelative(0.395f, -0.125f, 0.812f, -0.125f) + quadToRelative(0.417f, 0f, 0.812f, 0.125f) + quadToRelative(0.396f, 0.125f, 0.771f, 0.375f) + lineToRelative(10.459f, 7.834f) + quadToRelative(0.5f, 0.375f, 0.77f, 0.937f) + quadToRelative(0.271f, 0.563f, 0.271f, 1.188f) + verticalLineToRelative(15.708f) + quadToRelative(0f, 1.083f, -0.771f, 1.854f) + quadToRelative(-0.77f, 0.771f, -1.854f, 0.771f) + horizontalLineToRelative(-8.375f) + verticalLineTo(24.5f) + horizontalLineToRelative(-4.166f) + verticalLineToRelative(10.25f) + close() + moveTo(20f, 20.333f) + close() + } + }.build().also { _home = it } + } + } + + data object Filled { + private var _home: ImageVector? = null + + val Home: ImageVector + get() { + return _home ?: ImageVector.Builder( + name = "home", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 40.0f, + viewportHeight = 40.0f + ).apply { + path( + fill = SolidColor(Color.Black), + fillAlpha = 1f, + stroke = null, + strokeAlpha = 1f, + strokeLineWidth = 1.0f, + strokeLineCap = StrokeCap.Butt, + strokeLineJoin = StrokeJoin.Miter, + strokeLineMiter = 1f, + pathFillType = PathFillType.NonZero + ) { + moveTo(9.542f, 34.75f) + quadToRelative(-1.084f, 0f, -1.855f, -0.771f) + quadToRelative(-0.77f, -0.771f, -0.77f, -1.854f) + verticalLineTo(16.417f) + quadToRelative(0f, -0.625f, 0.271f, -1.188f) + quadToRelative(0.27f, -0.562f, 0.77f, -0.937f) + lineToRelative(10.459f, -7.834f) + quadToRelative(0.375f, -0.25f, 0.771f, -0.375f) + quadToRelative(0.395f, -0.125f, 0.812f, -0.125f) + quadToRelative(0.417f, 0f, 0.812f, 0.125f) + quadToRelative(0.396f, 0.125f, 0.771f, 0.375f) + lineToRelative(10.459f, 7.834f) + quadToRelative(0.5f, 0.375f, 0.77f, 0.937f) + quadToRelative(0.271f, 0.563f, 0.271f, 1.188f) + verticalLineToRelative(15.708f) + quadToRelative(0f, 1.083f, -0.771f, 1.854f) + quadToRelative(-0.77f, 0.771f, -1.854f, 0.771f) + horizontalLineToRelative(-7.041f) + verticalLineTo(23.208f) + horizontalLineToRelative(-6.792f) + verticalLineTo(34.75f) + close() + } + }.build().also { _home = it } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/PIPContent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/PIPContent.kt new file mode 100644 index 00000000..4106dcaa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/PIPContent.kt @@ -0,0 +1,40 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.PictureInPictureAlt +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.pip_unsupported +import org.jetbrains.compose.resources.stringResource + +@Composable +fun PIPContent() { + Surface( + modifier = Modifier.fillMaxSize() + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = Icons.Rounded.PictureInPictureAlt, + contentDescription = null + ) + Text( + text = stringResource(Res.string.pip_unsupported), + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.kt new file mode 100644 index 00000000..65ca0abc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.kt @@ -0,0 +1,55 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp + +@Composable +expect fun VerticalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier = Modifier, + style: ScrollbarStyle = localScrollbarStyle() +) + +@Composable +expect fun HorizontalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier = Modifier, + style: ScrollbarStyle = localScrollbarStyle() +) + +@Composable +expect fun rememberScrollbarAdapter( + scrollState: LazyGridState, +): ScrollbarAdapter + +@Composable +expect fun rememberScrollbarAdapter( + scrollState: LazyListState, +): ScrollbarAdapter + +expect interface ScrollbarAdapter { + val scrollOffset: Double + val contentSize: Double + val viewportSize: Double + + suspend fun scrollTo(scrollOffset: Double) +} + +@Immutable +data class ScrollbarStyle( + val minimalHeight: Dp, + val thickness: Dp, + val shape: Shape, + val hoverDurationMillis: Int, + val unhoverColor: Color, + val hoverColor: Color +) + +@Composable +expect fun localScrollbarStyle(): ScrollbarStyle \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/TintPainter.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/TintPainter.kt new file mode 100644 index 00000000..2558ec73 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/TintPainter.kt @@ -0,0 +1,27 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter + +class TintPainter( + private val painter: Painter, + private val tint: Color +): Painter() { + override val intrinsicSize: Size = painter.intrinsicSize + + override fun DrawScope.onDraw() { + with(painter) { + draw( + size = Size( + width = painter.intrinsicSize.width / 2F, + height = painter.intrinsicSize.height / 2F + ), + colorFilter = ColorFilter.tint(tint) + ) + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.kt new file mode 100644 index 00000000..4a1c1188 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.kt @@ -0,0 +1,6 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.runtime.Composable + +@Composable +expect fun AndroidFixWindowSize(content: @Composable () -> Unit) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/Component.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/Component.kt new file mode 100644 index 00000000..03aab5cb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/Component.kt @@ -0,0 +1,81 @@ +package dev.datlag.burningseries.ui.navigation + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.PictureInPicture +import androidx.compose.material.icons.rounded.PictureInPictureAlt +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.arkivanov.essenty.lifecycle.LifecycleOwner +import dev.datlag.burningseries.LocalDI +import dev.datlag.burningseries.PictureInPicture +import dev.datlag.burningseries.common.nullableFirebaseInstance +import dev.datlag.burningseries.common.screen +import dev.datlag.burningseries.ui.custom.PIPContent +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.tooling.compose.launchDefault +import dev.datlag.tooling.compose.launchIO +import dev.datlag.tooling.compose.launchMain +import dev.datlag.tooling.decompose.defaultScope +import dev.datlag.tooling.decompose.ioScope +import dev.datlag.tooling.decompose.lifecycle.LocalLifecycleOwner +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.datlag.tooling.decompose.mainScope +import kotlinx.coroutines.CoroutineScope +import org.kodein.di.DIAware + +interface Component : DIAware, ComponentContext { + + val handlesPIP: Boolean + get() = false + + @Composable + fun render() + + fun launchIO(block: suspend CoroutineScope.() -> Unit) = ioScope().launchIO(block) + fun launchMain(block: suspend CoroutineScope.() -> Unit) = mainScope().launchMain(block) + fun launchDefault(block: suspend CoroutineScope.() -> Unit) = defaultScope().launchDefault(block) + + @Composable + fun onRender(content: @Composable (Boolean) -> Unit) { + CompositionLocalProvider( + LocalDI provides di, + LocalLifecycleOwner provides object : LifecycleOwner { + override val lifecycle: Lifecycle = this@Component.lifecycle + } + ) { + Box { + val pip by PictureInPicture.collectAsStateWithLifecycle() + + content(pip) + if (pip && !handlesPIP) { + PIPContent() + } + } + } + SideEffect { + nullableFirebaseInstance()?.crashlytics?.screen(this) + } + } + + @Composable + fun onRenderWithScheme(key: Any?, content: @Composable (SchemeTheme.Updater?) -> Unit) { + onRender { + SchemeTheme(key = key, content = content) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/DialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/DialogComponent.kt new file mode 100644 index 00000000..5a403aba --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/DialogComponent.kt @@ -0,0 +1,19 @@ +package dev.datlag.burningseries.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import dev.datlag.burningseries.PictureInPicture +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle + +interface DialogComponent : Component { + fun dismiss() + + @Composable + override fun onRender(content: @Composable (Boolean) -> Unit) { + val isPip by PictureInPicture.collectAsStateWithLifecycle() + + if (!isPip || handlesPIP) { + super.onRender(content) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootComponent.kt new file mode 100644 index 00000000..ad43f6b6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootComponent.kt @@ -0,0 +1,159 @@ +package dev.datlag.burningseries.ui.navigation + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.bringToFront +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.replaceAll +import com.arkivanov.decompose.router.stack.replaceCurrent +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.ui.navigation.screen.activate.ActivateScreenComponent +import dev.datlag.burningseries.ui.navigation.screen.home.HomeScreenComponent +import dev.datlag.burningseries.ui.navigation.screen.medium.MediumScreenComponent +import dev.datlag.burningseries.ui.navigation.screen.video.VideoScreenComponent +import dev.datlag.burningseries.ui.navigation.screen.welcome.WelcomeScreenComponent +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.runBlocking +import org.kodein.di.DI +import org.kodein.di.instance + +class RootComponent( + componentContext: ComponentContext, + override val di: DI, + private val syncId: String? = null, + private val seriesHref: String? = null +) : Component, ComponentContext by componentContext { + + private val settings by instance() + + private val navigation = StackNavigation() + private val stack = childStack( + source = navigation, + serializer = RootConfig.serializer(), + initialStack = { + runBlocking { + settings.language.firstOrNull() + }.let { + if (it == null) { + listOf(RootConfig.Welcome) + } else { + val home = RootConfig.Home(syncId?.ifBlank { null }) + if (!seriesHref.isNullOrBlank()) { + listOf( + home, + RootConfig.Medium( + SeriesData.fromHref(seriesHref), + it + ) + ) + } else { + listOf(home) + } + } + } + }, + childFactory = ::createScreenComponent, + handleBackButton = true + ) + + private fun createScreenComponent( + rootConfig: RootConfig, + componentContext: ComponentContext + ): Component { + return when (rootConfig) { + is RootConfig.Welcome -> WelcomeScreenComponent( + componentContext = componentContext, + di = di, + onHome = { + navigation.replaceAll(RootConfig.Home(syncId?.ifBlank { null })) + } + ) + is RootConfig.Home -> HomeScreenComponent( + componentContext = componentContext, + di = di, + syncId = rootConfig.syncId, + onMedium = { data, lang -> + navigation.bringToFront(RootConfig.Medium(data, lang)) + } + ) + is RootConfig.Medium -> MediumScreenComponent( + componentContext = componentContext, + di = di, + initialSeriesData = rootConfig.seriesData, + initialIsAnime = rootConfig.isAnime, + initialLanguage = rootConfig.language, + onBack = navigation::pop, + onWatch = { series, episode, streams -> + navigation.bringToFront(RootConfig.Video(series, episode, streams.toImmutableSet())) + }, + onActivate = { series, episode -> + navigation.bringToFront(RootConfig.Activate(series, episode)) + } + ) + is RootConfig.Video -> VideoScreenComponent( + componentContext = componentContext, + di = di, + series = rootConfig.series, + episode = rootConfig.episode, + streams = rootConfig.streams, + onBack = navigation::pop, + onNext = { episode, streams -> + navigation.replaceCurrent(RootConfig.Video( + series = rootConfig.series, + episode = episode, + streams = streams.toImmutableSet() + )) + } + ) + is RootConfig.Activate -> ActivateScreenComponent( + componentContext = componentContext, + di = di, + series = rootConfig.series, + episode = rootConfig.episode, + onBack = navigation::pop, + onWatch = { series, episode, stream -> + navigation.bringToFront(RootConfig.Video(series, episode, stream.toImmutableSet())) + } + ) + } + } + + override val handlesPIP: Boolean = true + + @OptIn(ExperimentalDecomposeApi::class) + @Composable + override fun render() { + onRender { + Children( + stack = stack, + animation = predictiveBackAnimation( + backHandler = this.backHandler, + fallbackAnimation = stackAnimation(fade()), + onBack = { + navigation.pop() + } + ) + ) { + it.instance.render() + } + } + } + + fun onSync(id: String) { + navigation.replaceAll(RootConfig.Home(syncId = id.ifBlank { null })) + } + + fun onSeries(href: String) { + navigation.bringToFront(RootConfig.Medium(SeriesData.fromHref(href), null)) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootConfig.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootConfig.kt new file mode 100644 index 00000000..3c44e2d0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/RootConfig.kt @@ -0,0 +1,41 @@ +package dev.datlag.burningseries.ui.navigation + +import dev.datlag.burningseries.model.SearchItem +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.model.serializer.SerializableImmutableList +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.serialization.Serializable + +@Serializable +sealed class RootConfig { + + @Serializable + data object Welcome : RootConfig() + + @Serializable + data class Home(val syncId: String?) : RootConfig() + + @Serializable + data class Medium( + val seriesData: SeriesData, + val language: Language?, + val isAnime: Boolean = if (seriesData is SearchItem) seriesData.isAnime else false + ) : RootConfig() + + @Serializable + data class Video( + val series: Series, + val episode: Series.Episode, + val streams: SerializableImmutableSet + ) : RootConfig() + + @Serializable + data class Activate( + val series: Series, + val episode: Series.Episode + ) : RootConfig() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateComponent.kt new file mode 100644 index 00000000..299fadbc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateComponent.kt @@ -0,0 +1,31 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.value.Value +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.state.SaveState +import dev.datlag.burningseries.ui.navigation.Component +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.StateFlow + +interface ActivateComponent : Component { + val episode: Series.Episode + val saveState: StateFlow + + val dialog: Value> + + fun back() + fun onScraped(episodeHref: String?, data: String?) + fun success( + series: Series?, + episode: Series.Episode?, + stream: ImmutableCollection + ) + fun error( + series: Series?, + episode: Series.Episode?, + stream: ImmutableCollection + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.kt new file mode 100644 index 00000000..296d1c81 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.kt @@ -0,0 +1,6 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import androidx.compose.runtime.Composable + +@Composable +expect fun ActivateScreen(component: ActivateComponent) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreenComponent.kt new file mode 100644 index 00000000..686d7af5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreenComponent.kt @@ -0,0 +1,153 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.arkivanov.decompose.value.Value +import dev.datlag.burningseries.model.HosterScraping +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.SaveStateMachine +import dev.datlag.burningseries.network.state.SaveAction +import dev.datlag.burningseries.network.state.SaveState +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.burningseries.ui.navigation.screen.activate.dialog.error.ErrorDialogComponent +import dev.datlag.burningseries.ui.navigation.screen.activate.dialog.success.SuccessDialog +import dev.datlag.burningseries.ui.navigation.screen.activate.dialog.success.SuccessDialogComponent +import dev.datlag.skeo.DirectLink +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.compose.withMainContext +import dev.datlag.tooling.decompose.ioScope +import dev.datlag.tooling.scopeCatching +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.serialization.json.Json +import org.kodein.di.DI +import org.kodein.di.instance + +class ActivateScreenComponent( + componentContext: ComponentContext, + override val di: DI, + private val series: Series, + override val episode: Series.Episode, + private val onBack: () -> Unit, + private val onWatch: (Series, Series.Episode, ImmutableCollection) -> Unit +) : ActivateComponent, ComponentContext by componentContext { + + private val json by instance() + private val savedData: MutableSet = mutableSetOf() + private val saveStateMachine by instance() + + override val saveState: StateFlow = saveStateMachine.state.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = SaveState.None + ) + + private val dialogNavigation = SlotNavigation() + override val dialog: Value> = childSlot( + source = dialogNavigation, + serializer = DialogConfig.serializer() + ) { config, context -> + when (config) { + is DialogConfig.Success -> SuccessDialogComponent( + componentContext = context, + di = di, + stream = config.stream, + onDismiss = dialogNavigation::dismiss, + onBack = { + dialogNavigation.dismiss { + back() + } + }, + onWatch = { stream -> + dialogNavigation.dismiss { + onWatch(config.series ?: series, config.episode ?: episode, stream) + } + } + ) + is DialogConfig.Error -> ErrorDialogComponent( + componentContext = context, + di = di, + stream = config.stream, + onDismiss = dialogNavigation::dismiss, + onBack = { + dialogNavigation.dismiss { + back() + } + }, + onWatch = { stream -> + dialogNavigation.dismiss { + onWatch(config.series ?: series, config.episode ?: episode, stream) + } + } + ) + } + } + + @Composable + override fun render() { + onRender { + ActivateScreen(this) + } + } + + override fun back() { + onBack() + } + + override fun onScraped(episodeHref: String?, data: String?) { + val trimmed = data?.trim()?.ifBlank { null } ?: return + if (!trimmed.equals("null", true) && !trimmed.equals("undefined", true)) { + val converted = scopeCatching { + json.decodeFromString(trimmed) + }.getOrNull() + + if (converted != null) { + if (!savedData.contains(converted.href)) { + launchIO { + saveStateMachine.dispatch(SaveAction.Save(episodeHref, converted)) + } + } + + savedData.add(converted.href) + } + } + } + + override fun success( + series: Series?, + episode: Series.Episode?, + stream: ImmutableCollection + ) { + launchIO { + saveStateMachine.dispatch(SaveAction.Clear) + withMainContext { + dialogNavigation.activate(DialogConfig.Success(series, episode, stream)) + } + } + } + + override fun error( + series: Series?, + episode: Series.Episode?, + stream: ImmutableCollection + ) { + launchIO { + saveStateMachine.dispatch(SaveAction.Clear) + withMainContext { + dialogNavigation.activate(DialogConfig.Error(series, episode, stream)) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/DialogConfig.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/DialogConfig.kt new file mode 100644 index 00000000..02bc0417 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/DialogConfig.kt @@ -0,0 +1,24 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +import dev.datlag.burningseries.model.Series +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.serialization.Serializable + +@Serializable +sealed interface DialogConfig { + + @Serializable + data class Success( + val series: Series?, + val episode: Series.Episode?, + val stream: ImmutableCollection + ) : DialogConfig + + @Serializable + data class Error( + val series: Series?, + val episode: Series.Episode?, + val stream: ImmutableCollection + ) : DialogConfig +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorComponent.kt new file mode 100644 index 00000000..c8dac77f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorComponent.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.error + +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection + +interface ErrorComponent : DialogComponent { + val stream: ImmutableCollection + + fun back() + fun watch(stream: ImmutableCollection) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialog.kt new file mode 100644 index 00000000..93c93359 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialog.kt @@ -0,0 +1,101 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.error + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Error +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.Warning +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.activate_error_text +import dev.datlag.burningseries.composeapp.generated.resources.activate_success_text +import dev.datlag.burningseries.composeapp.generated.resources.back +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.datlag.burningseries.composeapp.generated.resources.error +import dev.datlag.burningseries.composeapp.generated.resources.success +import dev.datlag.burningseries.composeapp.generated.resources.warning +import dev.datlag.burningseries.composeapp.generated.resources.watch +import org.jetbrains.compose.resources.stringResource + +@Composable +fun ErrorDialog(component: ErrorComponent) { + val isWarning = remember { component.stream.isNotEmpty() } + + AlertDialog( + onDismissRequest = { + component.dismiss() + }, + icon = { + Icon( + imageVector = if (isWarning) Icons.Rounded.Warning else Icons.Rounded.Error, + contentDescription = null + ) + }, + title = { + Text( + text = stringResource(if (isWarning) Res.string.warning else Res.string.error), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + softWrap = true + ) + }, + text = { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = stringResource(Res.string.activate_error_text)) + component.stream.ifEmpty { null }?.let { stream -> + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { + component.watch(stream) + } + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.PlayArrow, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.watch)) + } + } + } + }, + dismissButton = { + TextButton( + onClick = { + component.back() + } + ) { + Text(text = stringResource(Res.string.back)) + } + }, + confirmButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.close)) + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialogComponent.kt new file mode 100644 index 00000000..b3e7371c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/error/ErrorDialogComponent.kt @@ -0,0 +1,36 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.error + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import org.kodein.di.DI + +class ErrorDialogComponent( + componentContext: ComponentContext, + override val di: DI, + override val stream: ImmutableCollection, + private val onDismiss: () -> Unit, + private val onBack: () -> Unit, + private val onWatch: (ImmutableCollection) -> Unit +) : ErrorComponent, ComponentContext by componentContext { + + @Composable + override fun render() { + onRender { + ErrorDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + + override fun back() { + onBack() + } + + override fun watch(stream: ImmutableCollection) { + onWatch(stream) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessComponent.kt new file mode 100644 index 00000000..0cf6d2bd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessComponent.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.success + +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection + +interface SuccessComponent : DialogComponent { + val stream: ImmutableCollection + + fun back() + fun watch(stream: ImmutableCollection) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialog.kt new file mode 100644 index 00000000..21a446f3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialog.kt @@ -0,0 +1,93 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.success + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.activate_success_text +import dev.datlag.burningseries.composeapp.generated.resources.back +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.datlag.burningseries.composeapp.generated.resources.success +import dev.datlag.burningseries.composeapp.generated.resources.watch +import org.jetbrains.compose.resources.stringResource + +@Composable +fun SuccessDialog(component: SuccessComponent) { + AlertDialog( + onDismissRequest = { + component.dismiss() + }, + icon = { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = null + ) + }, + title = { + Text( + text = stringResource(Res.string.success), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + softWrap = true + ) + }, + text = { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = stringResource(Res.string.activate_success_text)) + component.stream.ifEmpty { null }?.let { stream -> + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { + component.watch(stream) + } + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.PlayArrow, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.watch)) + } + } + } + }, + dismissButton = { + TextButton( + onClick = { + component.back() + } + ) { + Text(text = stringResource(Res.string.back)) + } + }, + confirmButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.close)) + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialogComponent.kt new file mode 100644 index 00000000..895ee1b9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/dialog/success/SuccessDialogComponent.kt @@ -0,0 +1,36 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate.dialog.success + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import org.kodein.di.DI + +class SuccessDialogComponent( + componentContext: ComponentContext, + override val di: DI, + override val stream: ImmutableCollection, + private val onDismiss: () -> Unit, + private val onBack: () -> Unit, + private val onWatch: (ImmutableCollection) -> Unit +) : SuccessComponent, ComponentContext by componentContext { + + @Composable + override fun render() { + onRender { + SuccessDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + + override fun back() { + onBack() + } + + override fun watch(stream: ImmutableCollection) { + onWatch(stream) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/CollapsingToolbar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/CollapsingToolbar.kt new file mode 100644 index 00000000..6c0a2de5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/CollapsingToolbar.kt @@ -0,0 +1,143 @@ +package dev.datlag.burningseries.ui.navigation.screen.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.TopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import coil3.compose.AsyncImage +import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.materials.HazeMaterials +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.app_name +import dev.datlag.burningseries.model.BSUtil +import dev.datlag.tooling.compose.ifFalse +import org.jetbrains.compose.resources.stringResource +import kotlin.math.max +import kotlin.math.min +import dev.datlag.burningseries.MokoRes +import dev.icerock.moko.resources.compose.painterResource + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) +@Composable +fun CollapsingToolbar( + state: TopAppBarState, + scrollBehavior: TopAppBarScrollBehavior, + onSettingsClick: () -> Unit +) { + Box( + modifier = Modifier.fillMaxWidth() + ) { + val isCollapsed by remember(state) { + derivedStateOf { state.collapsedFraction >= 0.99F } + } + val imageAlpha by remember(state) { + derivedStateOf { + max(min(1F - state.collapsedFraction, 1F), 0F) + } + } + val girlAlpha = remember(isCollapsed, imageAlpha) { + if (isCollapsed) 0F else min(imageAlpha * 2F, 1F) + } + + Image( + modifier = Modifier.fillMaxWidth().matchParentSize(), + painter = painterResource(MokoRes.images.background), + contentDescription = null, + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + alpha = imageAlpha + ) + + Image( + modifier = Modifier.fillMaxWidth().matchParentSize(), + painter = painterResource(MokoRes.images.lighting), + contentDescription = null, + contentScale = ContentScale.Crop, + alignment = Alignment.TopStart, + alpha = imageAlpha + ) + + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(MokoRes.images.title), + contentDescription = null, + alignment = Alignment.Center, + contentScale = ContentScale.Inside, + alpha = girlAlpha + ) + + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(MokoRes.images.girl_floating), + contentDescription = null, + alignment = Alignment.BottomEnd, + alpha = girlAlpha + ) + + LargeTopAppBar( + navigationIcon = { + IconButton( + modifier = Modifier.ifFalse(isCollapsed) { + background(MaterialTheme.colorScheme.surface.copy(alpha = 0.75F), CircleShape) + }, + onClick = { + onSettingsClick() + } + ) { + Icon( + imageVector = Icons.Rounded.Settings, + contentDescription = null + ) + } + }, + title = { + AnimatedVisibility( + visible = isCollapsed + ) { + Text( + text = stringResource(Res.string.app_name) + ) + } + }, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent, + ), + modifier = Modifier.hazeChild( + state = LocalHaze.current, + style = HazeMaterials.thin( + containerColor = MaterialTheme.colorScheme.surface, + ) + ).fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/HomeCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/HomeCard.kt new file mode 100644 index 00000000..65ddfe67 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/HomeCard.kt @@ -0,0 +1,177 @@ +package dev.datlag.burningseries.ui.navigation.screen.component + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import dev.datlag.burningseries.common.bottomShadowBrush +import dev.datlag.burningseries.common.onPrimary +import dev.datlag.burningseries.common.primary +import dev.datlag.burningseries.database.ExtendedSeries +import dev.datlag.burningseries.database.Series +import dev.datlag.burningseries.model.Home +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.burningseries.ui.theme.rememberSchemeThemeDominantColorState +import dev.datlag.tooling.compose.focusScale +import io.github.aakira.napier.Napier + +@Composable +fun HomeCard( + episode: Home.Episode, + modifier: Modifier = Modifier, + onClick: (Home.Episode) -> Unit +) { + SchemeTheme( + key = episode + ) { updater -> + val source = remember { MutableInteractionSource() } + Card( + modifier = modifier.focusScale( + scale = 1.05F, + interactionSource = source + ), + onClick = { + onClick(episode) + }, + interactionSource = source + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { + val colorState = rememberSchemeThemeDominantColorState( + key = episode.source, + applyMinContrast = true, + minContrastBackgroundColor = MaterialTheme.colorScheme.surfaceVariant, + ) + + AsyncImage( + model = episode.coverHref, + modifier = Modifier.fillMaxSize(), + contentDescription = episode.title, + contentScale = ContentScale.Crop, + onSuccess = { state -> + updater?.update(state.painter) + } + ) + + LanguageChip( + flag = episode.flags.firstOrNull(), + modifier = Modifier.padding(16.dp).align(Alignment.TopEnd) + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + .bottomShadowBrush(colorState.primary) + .padding(16.dp) + .padding(top = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = episode.mainTitle, + style = MaterialTheme.typography.titleLarge, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + color = colorState.onPrimary + ) + episode.subTitle?.let { sub -> + Text( + text = sub, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + color = colorState.onPrimary, + maxLines = 2 + ) + } + } + } + } + } +} + +@Composable +fun HomeCard( + series: SeriesData, + modifier: Modifier = Modifier, + onClick: (SeriesData) -> Unit +) { + SchemeTheme( + key = series + ) { updater -> + val source = remember { MutableInteractionSource() } + Card( + modifier = modifier.focusScale( + interactionSource = source + ), + onClick = { + onClick(series) + }, + interactionSource = source + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { + val colorState = rememberSchemeThemeDominantColorState( + key = series.source, + applyMinContrast = true, + minContrastBackgroundColor = MaterialTheme.colorScheme.surfaceVariant, + ) + + AsyncImage( + model = series.coverHref, + modifier = Modifier.fillMaxSize(), + contentDescription = series.title, + contentScale = ContentScale.Crop, + onSuccess = { state -> + updater?.update(state.painter) + } + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .fillMaxWidth() + .bottomShadowBrush(colorState.primary) + .padding(16.dp) + .padding(top = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = series.mainTitle, + style = MaterialTheme.typography.titleLarge, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + color = colorState.onPrimary + ) + series.subTitle?.let { sub -> + Text( + text = sub, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + color = colorState.onPrimary, + maxLines = 2 + ) + } + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/LanguageChip.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/LanguageChip.kt new file mode 100644 index 00000000..b6ca0c62 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/LanguageChip.kt @@ -0,0 +1,55 @@ +package dev.datlag.burningseries.ui.navigation.screen.component + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SuggestionChip +import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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 dev.datlag.burningseries.common.languageByCode +import dev.datlag.burningseries.model.Home +import dev.datlag.burningseries.other.CountryImage +import org.jetbrains.compose.resources.stringResource + +@Composable +fun LanguageChip( + flag: Home.Episode.Flag?, + modifier: Modifier = Modifier, + labelColor: Color = MaterialTheme.colorScheme.onPrimaryContainer, + containerColor: Color = MaterialTheme.colorScheme.primaryContainer +) { + if (flag != null) { + SuggestionChip( + onClick = { }, + modifier = modifier.wrapContentHeight().height(32.dp), + border = null, + colors = SuggestionChipDefaults.suggestionChipColors( + labelColor = labelColor, + containerColor = containerColor + ), + icon = { + flag.bestCountryCode?.let { + CountryImage.showFlags( + code = it, + iconSize = SuggestionChipDefaults.IconSize + ) + } + }, + label = { + Text( + text = flag.title ?: flag.languageByCode?.let { stringResource(it) } ?: "", + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Clip, + softWrap = true + ) + } + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/NavigationBarState.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/NavigationBarState.kt new file mode 100644 index 00000000..f78cdf7c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/component/NavigationBarState.kt @@ -0,0 +1,63 @@ +package dev.datlag.burningseries.ui.navigation.screen.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.FavoriteBorder +import androidx.compose.material.icons.rounded.Savings +import androidx.compose.ui.graphics.vector.ImageVector +import dev.datlag.burningseries.ui.custom.MaterialSymbols +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +sealed interface NavigationBarState { + + @Transient + val unselectedIcon: ImageVector + + @Transient + val selectedIcon: ImageVector + get() = unselectedIcon + + val sponsorIcon: ImageVector + get() = when (this) { + is Sponsor -> selectedIcon + else -> Sponsor.unselectedIcon + } + + val homeIcon: ImageVector + get() = when (this) { + is Home -> selectedIcon + else -> Home.unselectedIcon + } + + val favoriteIcon: ImageVector + get() = when (this) { + is Favorite -> selectedIcon + else -> Favorite.unselectedIcon + } + + @Serializable + data object Sponsor : NavigationBarState { + override val unselectedIcon: ImageVector + get() = Icons.Rounded.Savings + } + + @Serializable + data object Home : NavigationBarState { + override val unselectedIcon: ImageVector + get() = MaterialSymbols.Rounded.Home + + override val selectedIcon: ImageVector + get() = MaterialSymbols.Filled.Home + } + + @Serializable + data object Favorite : NavigationBarState { + override val unselectedIcon: ImageVector + get() = Icons.Rounded.FavoriteBorder + + override val selectedIcon: ImageVector + get() = Icons.Rounded.Favorite + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/DialogConfig.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/DialogConfig.kt new file mode 100644 index 00000000..7b0b6cf1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/DialogConfig.kt @@ -0,0 +1,20 @@ +package dev.datlag.burningseries.ui.navigation.screen.home + +import dev.datlag.burningseries.github.model.UserAndRelease +import kotlinx.serialization.Serializable + +@Serializable +sealed class DialogConfig { + + @Serializable + data object Settings : DialogConfig() + + @Serializable + data class Release(val release: UserAndRelease.Release) : DialogConfig() + + @Serializable + data object QrCode : DialogConfig() + + @Serializable + data class Sync(val id: String) : DialogConfig() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeComponent.kt new file mode 100644 index 00000000..34e14fef --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeComponent.kt @@ -0,0 +1,37 @@ +package dev.datlag.burningseries.ui.navigation.screen.home + +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.value.Value +import dev.datlag.burningseries.database.ExtendedSeries +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.network.state.HomeState +import dev.datlag.burningseries.network.state.SearchState +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.navigation.Component +import dev.datlag.burningseries.ui.navigation.DialogComponent +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface HomeComponent : Component { + val home: StateFlow + val search: StateFlow + val showFavorites: StateFlow + val favorites: StateFlow> + val language: Flow + + val release: Flow + val displayRelease: StateFlow + + val dialog: Value> + + fun details(data: SeriesData) = details(data, null) + fun details(data: SeriesData, language: Language?) + fun search(query: String?) + fun retryLoadingSearch() + fun toggleFavorites() + fun settings() + fun release(release: UserAndRelease.Release) + fun showQrCode() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreen.kt new file mode 100644 index 00000000..c4bc495d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreen.kt @@ -0,0 +1,47 @@ +package dev.datlag.burningseries.ui.navigation.screen.home + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import dev.datlag.burningseries.ui.custom.AndroidFixWindowSize +import dev.datlag.burningseries.ui.navigation.screen.home.component.CompactScreen +import dev.datlag.burningseries.ui.navigation.screen.home.component.HomeSearchBar +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle + +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class) +@Composable +fun HomeScreen(component: HomeComponent) { + AndroidFixWindowSize { + val dialogState by component.dialog.subscribeAsState() + val release by component.release.collectAsStateWithLifecycle(null) + val displayRelease by component.displayRelease.collectAsStateWithLifecycle() + + LaunchedEffect(release, displayRelease) { + if (displayRelease) { + release?.let(component::release) + } + } + + dialogState.child?.instance?.render() + + Scaffold( + topBar = { + HomeSearchBar(component) + }, + ) { padding -> + val state by component.home.collectAsStateWithLifecycle() + + CompactScreen( + state = state, + padding = padding, + component = component + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreenComponent.kt new file mode 100644 index 00000000..7c60c94f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/HomeScreenComponent.kt @@ -0,0 +1,178 @@ +package dev.datlag.burningseries.ui.navigation.screen.home + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.arkivanov.decompose.value.Value +import dev.chrisbanes.haze.HazeState +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.ExtendedSeries +import dev.datlag.burningseries.database.common.favoritesSeries +import dev.datlag.burningseries.database.common.favoritesSeriesOneShot +import dev.datlag.burningseries.database.common.seriesFullHref +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.network.HomeStateMachine +import dev.datlag.burningseries.network.SearchStateMachine +import dev.datlag.burningseries.network.state.HomeState +import dev.datlag.burningseries.network.state.SearchAction +import dev.datlag.burningseries.network.state.SearchState +import dev.datlag.burningseries.other.UserHelper +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.qrcode.QrCodeDialogComponent +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.release.ReleaseDialogComponent +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.SettingsDialogComponent +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.sync.SyncDialogComponent +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.decompose.ioScope +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import org.kodein.di.DI +import org.kodein.di.instance +import org.kodein.di.instanceOrNull + +class HomeScreenComponent( + componentContext: ComponentContext, + override val di: DI, + private val syncId: String?, + private val onMedium: (SeriesData, Language?) -> Unit +): HomeComponent, ComponentContext by componentContext { + + private val homeStateMachine by instance() + override val home: StateFlow = homeStateMachine.state.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = homeStateMachine.currentState + ) + + private val searchStateMachine by instance() + override val search: StateFlow = searchStateMachine.state.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = searchStateMachine.currentState + ) + + private val database by instance() + override val showFavorites: MutableStateFlow = MutableStateFlow(false) + override val favorites: StateFlow> = database.favoritesSeries( + ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = database.favoritesSeriesOneShot() + ) + + private val settings by instance() + override val language = settings.language.flowOn(ioDispatcher()) + + private val userHelper by instance() + override val release: Flow = userHelper.release + override val displayRelease: MutableStateFlow = MutableStateFlow(true) + + private val dialogNavigation = SlotNavigation() + override val dialog: Value> = childSlot( + source = dialogNavigation, + serializer = DialogConfig.serializer(), + initialConfiguration = { syncId?.ifBlank { null }?.let(DialogConfig::Sync) } + ) { config, context -> + when (config) { + is DialogConfig.Settings -> SettingsDialogComponent( + componentContext = context, + di = di, + onDismiss = dialogNavigation::dismiss + ) + is DialogConfig.Release -> ReleaseDialogComponent( + componentContext = context, + di = di, + release = config.release, + onDismiss = { + dialogNavigation.dismiss { + displayRelease.update { false } + } + } + ) + is DialogConfig.QrCode -> QrCodeDialogComponent( + componentContext = context, + di = di, + onDismiss = dialogNavigation::dismiss + ) + is DialogConfig.Sync -> SyncDialogComponent( + componentContext = context, + di = di, + connectId = config.id, + onDismiss = dialogNavigation::dismiss + ) + } + } + + @Composable + override fun render() { + val haze = remember { HazeState() } + + CompositionLocalProvider( + LocalHaze provides haze + ) { + onRender { + HomeScreen(this) + } + } + } + + override fun details(data: SeriesData, language: Language?) { + val savedSeries = database.seriesFullHref(data)?.let { href -> + data.shadowCopy(href = href) + } + if (savedSeries != null) { + onMedium(savedSeries, null) + } else { + onMedium(data, language) + } + } + + override fun search(query: String?) { + launchIO { + searchStateMachine.dispatch(SearchAction.Query(query?.ifBlank { null })) + } + } + + override fun retryLoadingSearch() { + launchIO { + searchStateMachine.dispatch(SearchAction.Retry) + } + } + + override fun toggleFavorites() { + showFavorites.update { !it } + } + + override fun settings() { + dialogNavigation.activate(DialogConfig.Settings) + } + + override fun release(release: UserAndRelease.Release) { + dialogNavigation.activate(DialogConfig.Release(release)) + } + + override fun showQrCode() { + dialogNavigation.activate(DialogConfig.QrCode) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.kt new file mode 100644 index 00000000..7d96a48c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.kt @@ -0,0 +1,6 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.runtime.Composable + +@Composable +expect fun AniFlowIconButton(onClick: () -> Unit) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/CompactScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/CompactScreen.kt new file mode 100644 index 00000000..64c99307 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/CompactScreen.kt @@ -0,0 +1,265 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.chrisbanes.haze.haze +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.common.merge +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.episodes +import dev.datlag.burningseries.composeapp.generated.resources.favorites +import dev.datlag.burningseries.composeapp.generated.resources.series +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.network.state.HomeState +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.custom.ErrorContent +import dev.datlag.burningseries.ui.navigation.screen.component.HomeCard +import dev.datlag.burningseries.ui.navigation.screen.home.HomeComponent +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.toImmutableList +import dev.datlag.burningseries.ui.custom.HorizontalScrollbar +import dev.datlag.burningseries.ui.custom.rememberScrollbarAdapter +import dev.datlag.burningseries.ui.custom.localScrollbarStyle +import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.platform.rememberIsTv +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@Composable +internal fun CompactScreen( + state: HomeState, + padding: PaddingValues, + component: HomeComponent +) { + val language by component.language.collectAsStateWithLifecycle(null) + val favorites by component.favorites.collectAsStateWithLifecycle() + val headerPadding = PaddingValues(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 4.dp) + val linksSupported by Platform.linksSupported.collectAsStateWithLifecycle() + val isDesktopOrTv = Platform.isDesktop || Platform.rememberIsTv() + + LazyColumn( + modifier = Modifier.fillMaxSize().haze(state = LocalHaze.current), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = padding + ) { + if (!linksSupported && !isDesktopOrTv) { + LinkSupportSection(headerPadding) + } + if (favorites.isNotEmpty()) { + item { + Text( + modifier = Modifier.padding(headerPadding), + text = stringResource(Res.string.favorites), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } + item { + val rowState = rememberLazyListState() + + Column( + modifier = Modifier.fillParentMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + LazyRow( + state = rowState, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + items( + items = favorites.toImmutableList(), + key = { it.href } + ) { + HomeCard( + series = it, + modifier = Modifier + .width(200.dp) + .height(280.dp), + onClick = component::details + ) + } + } + + HorizontalScrollbar( + adapter = rememberScrollbarAdapter(rowState), + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + style = localScrollbarStyle().copy( + unhoverColor = MaterialTheme.colorScheme.secondary, + hoverColor = MaterialTheme.colorScheme.primary + ) + ) + } + } + } + item { + Text( + modifier = Modifier.padding(headerPadding), + text = stringResource(Res.string.episodes), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } + item { + when { + state.isLoading -> { + Loading() + } + state.isEpisodeError -> { + ErrorContent( + modifier = Modifier.fillParentMaxWidth().padding(16.dp), + horizontal = true + ) + } + state is HomeState.Success -> { + val rowState = rememberLazyListState() + + Column( + modifier = Modifier.fillParentMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + LazyRow( + state = rowState, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + items( + items = (state as? HomeState.Success) + ?.home + ?.episodes + .orEmpty() + .sortedBy { Language.fromString(it.language)?.compareToNullable(language) } + .toImmutableList(), + key = { it.href } + ) { + HomeCard( + episode = it, + modifier = Modifier + .width(200.dp) + .height(280.dp), + onClick = component::details + ) + } + } + + HorizontalScrollbar( + adapter = rememberScrollbarAdapter(rowState), + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + style = localScrollbarStyle().copy( + unhoverColor = MaterialTheme.colorScheme.secondary, + hoverColor = MaterialTheme.colorScheme.primary + ) + ) + } + } + } + } + item { + Text( + modifier = Modifier.padding(headerPadding), + text = stringResource(Res.string.series), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } + item { + when { + state.isLoading -> { + Loading() + } + state.isSeriesError -> { + ErrorContent( + modifier = Modifier.fillParentMaxWidth().padding(16.dp), + horizontal = true + ) + } + state is HomeState.Success -> { + val rowState = rememberLazyListState() + + Column( + modifier = Modifier.fillParentMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + LazyRow( + state = rowState, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + items( + items = (state as? HomeState.Success) + ?.home + ?.series + .orEmpty() + .toImmutableList(), + key = { it.href } + ) { + HomeCard( + series = it, + modifier = Modifier + .width(200.dp) + .height(280.dp), + onClick = { data -> + component.details(data, language) + } + ) + } + } + + HorizontalScrollbar( + adapter = rememberScrollbarAdapter(rowState), + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + style = localScrollbarStyle().copy( + unhoverColor = MaterialTheme.colorScheme.secondary, + hoverColor = MaterialTheme.colorScheme.primary + ) + ) + } + } + } + } + } +} + +@Composable +private fun Loading() { + Box( + modifier = Modifier.fillMaxWidth().height(280.dp), + contentAlignment = Alignment.Center + ) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(fraction = 0.2F).clip(CircleShape) + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/HomeSearchBar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/HomeSearchBar.kt new file mode 100644 index 00000000..06be547d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/HomeSearchBar.kt @@ -0,0 +1,208 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +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.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.FavoriteBorder +import androidx.compose.material.icons.rounded.KeyboardArrowDown +import androidx.compose.material.icons.rounded.QrCode +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DockedSearchBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.SuggestionChip +import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.Text +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.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.common.merge +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.search +import dev.datlag.burningseries.network.state.SearchState +import dev.datlag.burningseries.other.AniFlow +import dev.datlag.burningseries.ui.navigation.screen.home.HomeComponent +import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.onClick +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.collections.immutable.toImmutableList +import org.jetbrains.compose.resources.stringResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.error +import dev.datlag.burningseries.composeapp.generated.resources.loading +import dev.datlag.burningseries.other.isInstalled + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun HomeSearchBar(component: HomeComponent) { + var query by remember { mutableStateOf("") } + val searchState by component.search.collectAsStateWithLifecycle() + val windowInsets = SearchBarDefaults.windowInsets.asPaddingValues().merge(16.dp) + var isActive by remember(searchState) { mutableStateOf(searchState.hasQueryItems) } + val isDesktopOrTv = Platform.isDesktop || Platform.rememberIsTv() + + DockedSearchBar( + modifier = Modifier + .fillMaxWidth() + .padding(windowInsets), + query = query, + onQueryChange = { + query = it + component.search(query) + }, + active = isActive, + enabled = searchState.isSuccess, + onActiveChange = { + if (it) { + isActive = searchState.hasQueryItems + if (searchState.isError) { + component.retryLoadingSearch() + } + } else { + isActive = false + } + }, + onSearch = { + query = it + component.search(it) + }, + leadingIcon = { + if (isActive) { + IconButton( + onClick = { + isActive = false + } + ) { + Icon( + imageVector = Icons.Rounded.KeyboardArrowDown, + contentDescription = null + ) + } + } else { + AniFlowIconButton( + onClick = { + component.search(query) + } + ) + } + }, + placeholder = { + val res = when { + searchState.isLoading -> Res.string.loading + searchState.isSuccess -> Res.string.search + else -> Res.string.error + } + + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(res), + textAlign = TextAlign.Center + ) + }, + trailingIcon = { + if (isActive) { + IconButton( + onClick = { + if (query.isEmpty()) { + isActive = false + } else { + query = "" + } + } + ) { + Icon( + imageVector = Icons.Rounded.Clear, + contentDescription = null + ) + } + } else { + val color = LocalContentColor.current.copy(alpha = 1F) + + if (isDesktopOrTv) { + IconButton( + onClick = { + component.showQrCode() + } + ) { + Icon( + imageVector = Icons.Rounded.QrCode, + contentDescription = null, + tint = color + ) + } + } else { + IconButton( + onClick = { + component.settings() + } + ) { + Icon( + imageVector = Icons.Rounded.Settings, + contentDescription = null, + tint = color + ) + } + } + } + }, + content = { + val language by component.language.collectAsStateWithLifecycle(null) + + (searchState as? SearchState.Success)?.let { current -> + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(1.dp) + ) { + items(current.queriedItems.toImmutableList(), key = { it.href }) { + SearchResult( + item = it, + modifier = Modifier.fillParentMaxWidth(), + onClick = { data -> + component.details(data, language) + } + ) + } + } + } + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.kt new file mode 100644 index 00000000..2401e445 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyListScope +import dev.datlag.tooling.Platform +import kotlinx.coroutines.flow.StateFlow + +expect val Platform.linksSupported: StateFlow + +expect fun LazyListScope.LinkSupportSection( + headerPadding: PaddingValues +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/SearchResult.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/SearchResult.kt new file mode 100644 index 00000000..7755df50 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/SearchResult.kt @@ -0,0 +1,71 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SuggestionChip +import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.model.SearchItem +import dev.datlag.tooling.compose.onClick + +@Composable +internal fun SearchResult( + item: SearchItem, + modifier: Modifier = Modifier, + onClick: (SearchItem) -> Unit +) { + Row( + modifier = modifier + .defaultMinSize(minHeight = 48.dp) + .clip(MaterialTheme.shapes.extraSmall) + .onClick { + onClick(item) + }.padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Column( + modifier = Modifier.fillMaxHeight().weight(1F), + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) + ) { + Text( + text = item.mainTitle, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.titleMedium, + maxLines = if (item.hasSubtitle) 1 else 2 + ) + item.subTitle?.let { sub -> + Text( + text = sub, + maxLines = 1 + ) + } + } + item.genre?.let { genre -> + SuggestionChip( + onClick = { }, + label = { Text(text = genre) }, + colors = SuggestionChipDefaults.suggestionChipColors( + containerColor = if (item.isAnime) { + MaterialTheme.colorScheme.primary + } else Color.Unspecified, + labelColor = if (item.isAnime) { + MaterialTheme.colorScheme.onPrimary + } else Color.Unspecified ), + border = if (item.isAnime) null else SuggestionChipDefaults.suggestionChipBorder(true) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeComponent.kt new file mode 100644 index 00000000..41d8a94e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeComponent.kt @@ -0,0 +1,9 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.qrcode + +import dev.datlag.burningseries.ui.navigation.DialogComponent +import kotlinx.coroutines.flow.StateFlow + +interface QrCodeComponent : DialogComponent { + val identifier: String + val syncedSettings: StateFlow +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialog.kt new file mode 100644 index 00000000..0c8bf521 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialog.kt @@ -0,0 +1,124 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.qrcode + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.QrCode +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.datlag.nanoid.NanoIdUtils +import dev.icerock.moko.resources.compose.painterResource +import io.github.alexzhirkevich.qrose.options.QrBallShape +import io.github.alexzhirkevich.qrose.options.QrBrush +import io.github.alexzhirkevich.qrose.options.QrColors +import io.github.alexzhirkevich.qrose.options.QrFrameShape +import io.github.alexzhirkevich.qrose.options.QrPixelShape +import io.github.alexzhirkevich.qrose.options.circle +import io.github.alexzhirkevich.qrose.options.solid +import io.github.alexzhirkevich.qrose.rememberQrCodePainter +import org.jetbrains.compose.resources.stringResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_finished +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_wait +import dev.datlag.burningseries.other.Constants +import dev.datlag.burningseries.ui.custom.TintPainter +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import io.github.alexzhirkevich.qrose.options.QrLogoPadding +import io.github.alexzhirkevich.qrose.toImageBitmap + +@Composable +fun QrCodeDialog(component: QrCodeComponent) { + val settingsSynced by component.syncedSettings.collectAsStateWithLifecycle() + + AlertDialog( + onDismissRequest = { }, + icon = { + if (settingsSynced) { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = null + ) + } else { + Icon( + imageVector = Icons.Rounded.QrCode, + contentDescription = null + ) + } + }, + title = { + Text(text = stringResource(Res.string.sync_settings)) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically) + ) { + if (settingsSynced) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sync_settings_finished), + textAlign = TextAlign.Center + ) + } else { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sync_settings_wait), + textAlign = TextAlign.Center + ) + + val contentColor = LocalContentColor.current + val logo = painterResource(MokoRes.images.Logo) + Image( + modifier = Modifier.size(200.dp), + painter = rememberQrCodePainter("${Constants.SYNCING_URL}${component.identifier}") { + logo { + painter = TintPainter(logo, contentColor) + padding = QrLogoPadding.Natural(0.1F) + } + shapes { + ball = QrBallShape.circle() + frame = QrFrameShape.circle() + darkPixel = QrPixelShape.circle() + } + colors { + ball = QrBrush.solid(contentColor) + frame = QrBrush.solid(contentColor) + dark = QrBrush.solid(contentColor) + } + }, + contentDescription = null + ) + } + } + }, + confirmButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.close)) + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialogComponent.kt new file mode 100644 index 00000000..12db82ff --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/qrcode/QrCodeDialogComponent.kt @@ -0,0 +1,76 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.qrcode + +import androidx.compose.runtime.Composable +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.essenty.lifecycle.doOnDestroy +import dev.datlag.burningseries.k2k.connect.connection +import dev.datlag.burningseries.k2k.discover.discovery +import dev.datlag.burningseries.other.SyncHelper +import dev.datlag.nanoid.NanoIdUtils +import dev.datlag.tooling.Platform +import dev.datlag.tooling.decompose.ioScope +import io.github.aakira.napier.Napier +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.kodein.di.DI +import org.kodein.di.instance + +class QrCodeDialogComponent( + componentContext: ComponentContext, + override val di: DI, + private val onDismiss: () -> Unit +) : QrCodeComponent, ComponentContext by componentContext { + + private val syncHelper by instance() + override val syncedSettings: MutableStateFlow = MutableStateFlow(false) + override val identifier: String = NanoIdUtils.randomNanoId() + private val discovery = ioScope().discovery { + setPort(7331) + setDiscoverableTimeout(0L) + setDiscoveryTimeout(1L) // builder requires value, stop immediately if used for discovery + } + + private val connection = ioScope().connection { + setPort(7337) + } + private val deviceName by instance("DEVICE_NAME") + + init { + discovery.makeDiscoverable( + hostName = deviceName, + filterMatch = identifier + ) + + connection.startReceiving { bytes -> + if (syncHelper.updateSettingsFromByteArray(bytes)) { + syncedSettings.update { true } + } + } + + doOnDestroy { + discovery.stopDiscovery() + discovery.stopBeingDiscoverable() + connection.stopReceiving() + } + } + + + @Composable + override fun render() { + onRender { + QrCodeDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + +} + diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseComponent.kt new file mode 100644 index 00000000..020a73e9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseComponent.kt @@ -0,0 +1,8 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.release + +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.ui.navigation.DialogComponent + +interface ReleaseComponent : DialogComponent { + val release: UserAndRelease.Release +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.kt new file mode 100644 index 00000000..40b25f21 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.kt @@ -0,0 +1,11 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.release + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +expect fun ReleaseDialog(component: ReleaseComponent) + +internal expect val DeviceIcon: ImageVector + @Composable + get \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialogComponent.kt new file mode 100644 index 00000000..1606ce43 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialogComponent.kt @@ -0,0 +1,25 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.release + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.burningseries.github.model.UserAndRelease +import org.kodein.di.DI + +class ReleaseDialogComponent( + componentContext: ComponentContext, + override val di: DI, + override val release: UserAndRelease.Release, + private val onDismiss: () -> Unit +) : ReleaseComponent, ComponentContext by componentContext { + + @Composable + override fun render() { + onRender { + ReleaseDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsComponent.kt new file mode 100644 index 00000000..1a0ccb4b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsComponent.kt @@ -0,0 +1,16 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings + +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.navigation.DialogComponent +import kotlinx.coroutines.flow.Flow + +interface SettingsComponent : DialogComponent { + + val language: Flow + val user: Flow + + fun setLanguage(language: Language) + fun login() + fun logout() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialog.kt new file mode 100644 index 00000000..0a87c6d1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialog.kt @@ -0,0 +1,97 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.LocalEdgeToEdge +import dev.datlag.burningseries.common.isFullyExpandedOrTargeted +import dev.datlag.burningseries.common.merge +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.GitHubOwnerSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.GitHubRepoSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.InfoSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.LanguageSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.LoginSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.SponsorSection +import dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component.SyncSection +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsDialog(component: SettingsComponent) { + val sheetState = rememberModalBottomSheetState() + val (insets, bottomPadding) = if (LocalEdgeToEdge.current) { + WindowInsets( + left = 0, + top = 0, + right = 0, + bottom = 0 + ) to BottomSheetDefaults.windowInsets.only(WindowInsetsSides.Bottom).asPaddingValues() + } else { + BottomSheetDefaults.windowInsets to PaddingValues() + } + + ModalBottomSheet( + onDismissRequest = component::dismiss, + windowInsets = insets, + sheetState = sheetState + ) { + val userState by component.user.collectAsStateWithLifecycle(null) + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + contentPadding = bottomPadding.merge(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + item { + InfoSection( + dismissVisible = sheetState.isFullyExpandedOrTargeted(forceFullExpand = true), + user = userState, + modifier = Modifier.fillParentMaxWidth(), + onDismiss = component::dismiss + ) + } + item { + LanguageSection( + languageFlow = component.language, + modifier = Modifier.fillParentMaxWidth(), + onSelect = component::setLanguage + ) + } + item { + LoginSection( + isLoggedIn = userState != null, + modifier = Modifier.fillParentMaxWidth(), + onLogin = { + component.login() + }, + onLogout = { + component.logout() + } + ) + } + SyncSection() + item { + SponsorSection(modifier = Modifier.fillParentMaxWidth()) + } + item { + GitHubRepoSection(modifier = Modifier.fillParentMaxWidth()) + } + item { + GitHubOwnerSection(modifier = Modifier.fillParentMaxWidth()) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialogComponent.kt new file mode 100644 index 00000000..c3e12c92 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/SettingsDialogComponent.kt @@ -0,0 +1,63 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.other.UserHelper +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.compose.withMainContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import org.kodein.di.DI +import org.kodein.di.instance +import org.publicvalue.multiplatform.oidc.ExperimentalOpenIdConnect +import org.publicvalue.multiplatform.oidc.flows.CodeAuthFlow + +class SettingsDialogComponent( + componentContext: ComponentContext, + override val di: DI, + private val onDismiss: () -> Unit +) : SettingsComponent, ComponentContext by componentContext { + + private val settings by instance() + override val language: Flow = settings.language.flowOn(ioDispatcher()) + + private val userHelper by instance() + private val authFlow by instance() + override val user: Flow = userHelper.user + + @Composable + override fun render() { + onRender { + SettingsDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + + override fun setLanguage(language: Language) { + launchIO { + settings.setLanguage(language) + } + } + + @OptIn(ExperimentalOpenIdConnect::class) + override fun login() { + launchIO { + userHelper.login(authFlow) + } + } + + override fun logout() { + launchIO { + userHelper.logout() + withMainContext { + onDismiss() + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubOwnerSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubOwnerSection.kt new file mode 100644 index 00000000..37406bec --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubOwnerSection.kt @@ -0,0 +1,46 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Code +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.developed_by_datlag +import dev.datlag.burningseries.other.Constants +import dev.datlag.tooling.compose.onClick +import org.jetbrains.compose.resources.stringResource + +@Composable +fun GitHubOwnerSection( + modifier: Modifier = Modifier, +) { + val uriHandler = LocalUriHandler.current + + Row( + modifier = modifier + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.medium) + .onClick { + uriHandler.openUri(Constants.GITHUB_OWNER) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Rounded.Code, + contentDescription = null, + ) + Text(text = stringResource(Res.string.developed_by_datlag)) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubRepoSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubRepoSection.kt new file mode 100644 index 00000000..b10d2031 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/GitHubRepoSection.kt @@ -0,0 +1,53 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Code +import androidx.compose.material.icons.rounded.Inventory2 +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.other.Constants +import dev.datlag.tooling.compose.onClick +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.github_repository +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource + +@Composable +fun GitHubRepoSection( + modifier: Modifier = Modifier, +) { + val uriHandler = LocalUriHandler.current + + Row( + modifier = modifier + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.small) + .onClick { + uriHandler.openUri(Constants.GITHUB_REPO) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Rounded.Inventory2, + contentDescription = null, + ) + Text(text = stringResource(Res.string.github_repository)) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/InfoSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/InfoSection.kt new file mode 100644 index 00000000..e2508b29 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/InfoSection.kt @@ -0,0 +1,108 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.icons.rounded.StarBorder +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.app_name +import dev.datlag.burningseries.github.model.UserAndRelease +import dev.datlag.burningseries.other.Constants + +@Composable +fun InfoSection( + dismissVisible: Boolean, + user: UserAndRelease.User?, + modifier: Modifier = Modifier, + onDismiss: () -> Unit +) { + Column( + modifier = modifier.padding(bottom = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + contentAlignment = Alignment.Center + ) { + this@Column.AnimatedVisibility( + modifier = Modifier.align(Alignment.CenterStart), + visible = dismissVisible, + enter = fadeIn(), + exit = fadeOut() + ) { + IconButton( + onClick = onDismiss + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = stringResource(Res.string.close) + ) + } + } + + AsyncImage( + modifier = Modifier.size(96.dp).clip(CircleShape), + model = user?.avatar, + contentDescription = null, + error = painterResource(MokoRes.images.AppIcon), + placeholder = painterResource(MokoRes.images.AppIcon), + contentScale = ContentScale.Crop, + alignment = Alignment.Center + ) + + this@Column.AnimatedVisibility( + modifier = Modifier.align(Alignment.CenterEnd), + visible = user != null, + enter = fadeIn(), + exit = fadeOut() + ) { + val uriHandler = LocalUriHandler.current + + IconButton( + onClick = { + uriHandler.openUri(Constants.GITHUB_REPO) + } + ) { + Icon( + imageVector = if (user?.hasStarred == true) Icons.Rounded.Star else Icons.Rounded.StarBorder, + contentDescription = null + ) + } + } + } + Text( + text = user?.name ?: stringResource(Res.string.app_name), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LanguageSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LanguageSection.kt new file mode 100644 index 00000000..96441cb7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LanguageSection.kt @@ -0,0 +1,145 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material.icons.rounded.Info +import androidx.compose.material.icons.rounded.Translate +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.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.unit.dp +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.datlag.burningseries.common.flags +import dev.datlag.burningseries.common.title +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.select_default_language_text +import dev.datlag.burningseries.composeapp.generated.resources.select_language +import dev.datlag.burningseries.other.CountryImage +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.Flow +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LanguageSection( + languageFlow: Flow, + modifier: Modifier = Modifier, + onSelect: (Language) -> Unit +) { + val selectedLanguage by languageFlow.collectAsStateWithLifecycle(null) + val useCase = rememberUseCaseState() + val languages = remember { Language.all.toImmutableList() } + + OptionDialog( + state = useCase, + selection = OptionSelection.Single( + options = languages.map { lang -> + Option( + selected = lang == selectedLanguage, + titleText = stringResource(lang.title), + customView = { selected -> + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val textColor = if (selected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface + } + + Box( + modifier = Modifier.padding(start = 24.dp), + contentAlignment = Alignment.Center + ) { + CountryImage.showFlags( + code = lang.code, + showBorder = true, + iconSize = 24.dp + ) + } + Text( + modifier = Modifier + .padding( + top = 16.dp, + bottom = 16.dp, + start = 16.dp + ) + .fillMaxWidth() + .wrapContentHeight(), + text = stringResource(lang.title), + maxLines = 1, + style = MaterialTheme.typography.labelLarge, + color = textColor + ) + } + } + ) + }, + onSelectOption = { option, _ -> + onSelect(languages[option]) + } + ), + config = OptionConfig( + mode = DisplayMode.LIST + ), + header = Header.Default( + icon = IconSource(imageVector = Icons.Rounded.Translate), + title = stringResource(Res.string.select_language) + ) + ) + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Rounded.Translate, + contentDescription = null + ) + Text( + text = stringResource(selectedLanguage?.title ?: Res.string.select_language) + ) + Spacer(modifier = Modifier.weight(1F)) + IconButton( + onClick = { useCase.show() } + ) { + Icon( + imageVector = Icons.Rounded.ExpandMore, + contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LoginSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LoginSection.kt new file mode 100644 index 00000000..3c0f63bd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/LoginSection.kt @@ -0,0 +1,71 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.NotInterested +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.unit.dp +import dev.datlag.tooling.compose.onClick +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.login +import dev.datlag.burningseries.composeapp.generated.resources.logout +import org.jetbrains.compose.resources.stringResource + +@Composable +fun LoginSection( + isLoggedIn: Boolean, + modifier: Modifier = Modifier, + onLogin: () -> Unit, + onLogout: () -> Unit, +) { + Row( + modifier = modifier + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.small) + .onClick { + if (isLoggedIn) { + onLogout() + } else { + onLogin() + } + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (isLoggedIn) { + Icon( + imageVector = Icons.Rounded.NotInterested, + contentDescription = null + ) + Text(text = stringResource(Res.string.logout)) + } else { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(MokoRes.images.github), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + Text(text = stringResource(Res.string.login)) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SponsorSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SponsorSection.kt new file mode 100644 index 00000000..173dad54 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SponsorSection.kt @@ -0,0 +1,96 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Savings +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.icerock.moko.resources.compose.painterResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.github +import dev.datlag.burningseries.composeapp.generated.resources.patreon +import dev.datlag.burningseries.composeapp.generated.resources.polar +import dev.datlag.burningseries.composeapp.generated.resources.sponsor +import dev.datlag.burningseries.other.Constants +import dev.datlag.tooling.compose.onClick +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SponsorSection( + modifier: Modifier = Modifier, +) { + val sponsorDialog = rememberUseCaseState() + val uriHandler = LocalUriHandler.current + + OptionDialog( + state = sponsorDialog, + config = OptionConfig( + mode = DisplayMode.LIST + ), + selection = OptionSelection.Single( + options = listOf( + Option( + icon = IconSource(painter = painterResource(MokoRes.images.github)), + titleText = stringResource(Res.string.github) + ), + Option( + icon = IconSource(painter = painterResource(MokoRes.images.polar)), + titleText = stringResource(Res.string.polar) + ), + Option( + icon = IconSource(painter = painterResource(MokoRes.images.patreon)), + titleText = stringResource(Res.string.patreon) + ) + ), + onSelectOption = { option, _ -> + when (option) { + 0 -> uriHandler.openUri(Constants.Sponsor.GITHUB) + 1 -> uriHandler.openUri(Constants.Sponsor.POLAR) + 2 -> uriHandler.openUri(Constants.Sponsor.PATREON) + } + } + ), + header = Header.Default( + title = stringResource(Res.string.sponsor), + icon = IconSource(imageVector = Icons.Rounded.Savings) + ) + ) + + Row( + modifier = modifier + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.medium) + .onClick { + sponsorDialog.show() + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Rounded.Savings, + contentDescription = null, + ) + Text(text = stringResource(Res.string.sponsor)) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.kt new file mode 100644 index 00000000..d91dbb0c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.kt @@ -0,0 +1,6 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.ui.Modifier + +expect fun LazyListScope.SyncSection() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncComponent.kt new file mode 100644 index 00000000..ad525625 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncComponent.kt @@ -0,0 +1,11 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.sync + +import dev.datlag.burningseries.ui.navigation.DialogComponent +import kotlinx.coroutines.flow.StateFlow + +interface SyncComponent : DialogComponent { + val connectId: String + val deviceNotFound: StateFlow + val sendingTo: StateFlow + val takingTime: StateFlow +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialog.kt new file mode 100644 index 00000000..bfb84f07 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialog.kt @@ -0,0 +1,100 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.sync + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Phonelink +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_connecting +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_not_found +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_sending +import dev.datlag.burningseries.composeapp.generated.resources.sync_settings_time +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource + +@Composable +fun SyncDialog(component: SyncComponent) { + val sendingTo by component.sendingTo.collectAsStateWithLifecycle() + + AlertDialog( + onDismissRequest = { }, + icon = { + Box( + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Rounded.Phonelink, + contentDescription = null + ) + if (!sendingTo.isNullOrBlank()) { + CircularProgressIndicator( + strokeWidth = 2.dp + ) + } + } + }, + title = { + Text(text = stringResource(Res.string.sync_settings)) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically) + ) { + val deviceNotFound by component.deviceNotFound.collectAsStateWithLifecycle() + val takingTime by component.takingTime.collectAsStateWithLifecycle() + + if (deviceNotFound) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sync_settings_not_found), + textAlign = TextAlign.Center + ) + } else { + val text = sendingTo?.ifBlank { null }?.let { + stringResource(Res.string.sync_settings_sending, it) + } ?: stringResource(Res.string.sync_settings_connecting) + + Text( + modifier = Modifier.fillMaxWidth(), + text = text, + textAlign = TextAlign.Center + ) + + if (takingTime) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sync_settings_time), + textAlign = TextAlign.Center + ) + } + } + } + }, + confirmButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.close)) + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialogComponent.kt new file mode 100644 index 00000000..b1c96c10 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/sync/SyncDialogComponent.kt @@ -0,0 +1,99 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.sync + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.essenty.lifecycle.doOnDestroy +import dev.datlag.burningseries.k2k.Host +import dev.datlag.burningseries.k2k.connect.Connection +import dev.datlag.burningseries.k2k.connect.connection +import dev.datlag.burningseries.k2k.discover.discovery +import dev.datlag.burningseries.other.SyncHelper +import dev.datlag.tooling.decompose.ioScope +import io.github.aakira.napier.Napier +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive +import org.kodein.di.DI +import org.kodein.di.instance + +class SyncDialogComponent( + componentContext: ComponentContext, + override val di: DI, + override val connectId: String, + private val onDismiss: () -> Unit +) : SyncComponent, ComponentContext by componentContext { + + private val syncHelper by instance() + override val deviceNotFound: MutableStateFlow = MutableStateFlow(false) + override val sendingTo: MutableStateFlow = MutableStateFlow(null) + override val takingTime: MutableStateFlow = MutableStateFlow(false) + private val discovery = ioScope().discovery { + setPort(7331) + setDiscoveryTimeout(5000L) + setDiscoveryTimeoutListener { + deviceNotFound.update { true } + } + setDiscoverableTimeout(1L) + setHostIsClient(false) + setHostFilter("^$connectId$".toRegex()) + } + private val connect = ioScope().connection { + setPort(7337) + } + + init { + discovery.startDiscovery() + + doOnDestroy { + discovery.stopDiscovery() + discovery.stopBeingDiscoverable() + connect.stopReceiving() + } + + launchIO { + val matchingPeer = discovery.peers.firstOrNull { it.size >= 1 }?.firstOrNull() + + matchingPeer?.let { + connect(it) + } ?: deviceNotFound.update { true } + } + } + + @Composable + override fun render() { + onRender { + SyncDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + + private suspend fun connect(host: Host) { + discovery.stopDiscovery() + + takingTime.update { false } + sendingTo.update { host.name } + deviceNotFound.update { false } + var counter = 0 + var syncData = syncHelper.encodeSettingsToByteArray() + while (currentCoroutineContext().isActive) { + if (syncData.isEmpty()) { + syncData = syncHelper.encodeSettingsToByteArray() + } + connect.send(syncData, host) + delay(3000) + if (counter >= 5) { + takingTime.update { true } + } + counter++ + } + takingTime.update { false } + sendingTo.update { null } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/DialogConfig.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/DialogConfig.kt new file mode 100644 index 00000000..14fee401 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/DialogConfig.kt @@ -0,0 +1,23 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium + +import dev.datlag.burningseries.model.Series +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.serialization.Serializable + +@Serializable +sealed interface DialogConfig { + + @Serializable + data class Activate( + val series: Series, + val episode: Series.Episode + ) : DialogConfig + + @Serializable + data class Sponsor( + val series: Series, + val episode: Series.Episode, + val streams: ImmutableCollection + ) : DialogConfig +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumComponent.kt new file mode 100644 index 00000000..b5afb649 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumComponent.kt @@ -0,0 +1,60 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium + +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.value.Value +import dev.datlag.burningseries.database.CombinedEpisode +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.burningseries.network.state.SeriesState +import dev.datlag.burningseries.ui.navigation.Component +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface MediumComponent : Component { + val seriesData: SeriesData + val initialIsAnime: Boolean + + val seriesState: StateFlow + val seriesTitle: Flow + val seriesSubTitle: Flow + val seriesCover: Flow + val seriesInfo: Flow> + val seriesSeason: Flow + val seriesSeasonList: Flow> + val seriesLanguage: Flow + val seriesLanguageList: Flow> + val seriesDescription: Flow + val seriesIsAnime: Flow + val combinedEpisodes: Flow> + val nextCombinedEpisode: Flow + + val episodeState: StateFlow + + val dialog: Value> + + val isFavorite: StateFlow + + fun back() + fun season(value: Series.Season) + fun language(value: Series.Language) + fun episode(episode: Series.Episode) + fun episode(combinedEpisode: CombinedEpisode) = episode(combinedEpisode.default) + fun watched(series: Series, episode: Series.Episode) + fun watched(series: Series, combinedEpisode: CombinedEpisode) = watched(series, combinedEpisode.default) + fun unwatched(series: Series, episode: Series.Episode) + fun unwatched(series: Series, combinedEpisode: CombinedEpisode) = unwatched(series, combinedEpisode.default) + fun activate(series: Series, episode: Series.Episode) + fun activate(series: Series, episode: CombinedEpisode) = activate(series, episode.default) + fun setFavorite(series: Series) + fun unsetFavorite(series: Series) + fun showSponsoringOrWatch( + series: Series, + episode: Series.Episode, + streams: ImmutableCollection + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreen.kt new file mode 100644 index 00000000..e24d5da4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreen.kt @@ -0,0 +1,231 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.PlayCircle +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.materialkolor.ktx.darken +import com.materialkolor.ktx.lighten +import dev.chrisbanes.haze.haze +import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.materials.HazeMaterials +import dev.datlag.burningseries.LocalDarkMode +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.common.plus +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.common.scrollUpVisible +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.burningseries.network.state.SeriesState +import dev.datlag.burningseries.other.AniFlow +import dev.datlag.burningseries.other.isInstalled +import dev.datlag.burningseries.ui.custom.ErrorContent +import dev.datlag.burningseries.ui.navigation.screen.medium.component.AniFlowCard +import dev.datlag.burningseries.ui.navigation.screen.medium.component.CoverSection +import dev.datlag.burningseries.ui.navigation.screen.medium.component.DescriptionSection +import dev.datlag.burningseries.ui.navigation.screen.medium.component.EpisodeItem +import dev.datlag.burningseries.ui.navigation.screen.medium.component.SeasonLanguageSection +import dev.datlag.burningseries.ui.navigation.screen.medium.component.Toolbar +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.tooling.Platform +import dev.datlag.tooling.compose.focusScale +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) +@Composable +fun MediumScreen(component: MediumComponent, updater: SchemeTheme.Updater?) { + val listState = rememberLazyListState() + val episodeState by component.episodeState.collectAsStateWithLifecycle() + val dialogState by component.dialog.subscribeAsState() + val seriesState by component.seriesState.collectAsStateWithLifecycle() + + when (val current = episodeState) { + is EpisodeState.SuccessStream -> { + component.showSponsoringOrWatch( + series = (seriesState as SeriesState.Success).series, + episode = current.episode, + streams = current.results + ) + } + is EpisodeState.ErrorHoster -> { + component.activate( + series = (seriesState as SeriesState.Success).series, + episode = current.episode + ) + } + is EpisodeState.ErrorStream -> { + component.activate( + series = (seriesState as SeriesState.Success).series, + episode = current.episode + ) + } + else -> { } + } + val loadingEpisode = remember(episodeState) { + when (val current = episodeState) { + is EpisodeState.Loading -> current.episode + is EpisodeState.SuccessHoster -> current.episode + else -> null + } + } + + dialogState.child?.instance?.render() + + Scaffold( + topBar = { + Toolbar( + component = component, + series = (seriesState as? SeriesState.Success)?.series + ) + }, + floatingActionButton = { + val nextEpisode by component.nextCombinedEpisode.collectAsStateWithLifecycle(null) + + nextEpisode?.ifHasHoster()?.let { episode -> + ExtendedFloatingActionButton( + onClick = { + component.episode(episode) + }, + expanded = listState.scrollUpVisible(), + icon = { + if (episode.isSame(loadingEpisode)) { + CircularProgressIndicator( + modifier = Modifier.size(ButtonDefaults.IconSize), + strokeWidth = 2.dp + ) + } else { + Icon( + imageVector = Icons.Rounded.PlayArrow, + contentDescription = null + ) + } + }, + text = { + Text( + text = episode.mainTitle, + maxLines = 1, + softWrap = true, + overflow = TextOverflow.Ellipsis + ) + } + ) + } + } + ) { padding -> + val isAnime by component.seriesIsAnime.collectAsStateWithLifecycle(component.initialIsAnime) + val isAndroidPhone = Platform.isAndroidJvm && !Platform.rememberIsTv() + val isAniFlowInstalled = AniFlow.isInstalled() + val episodes by component.combinedEpisodes.collectAsStateWithLifecycle(persistentListOf()) + + LazyColumn( + modifier = Modifier.fillMaxSize().haze(LocalHaze.current), + state = listState, + verticalArrangement = Arrangement.spacedBy(1.dp), + contentPadding = padding.plus(PaddingValues(top = 16.dp)) + ) { + item { + CoverSection( + component = component, + updater = updater, + modifier = Modifier.fillParentMaxWidth().padding(horizontal = 16.dp) + ) + } + item { + SeasonLanguageSection( + component = component, + modifier = Modifier.fillParentMaxWidth().padding(horizontal = 16.dp).padding(top = 8.dp) + ) + } + if (isAndroidPhone && isAnime && !isAniFlowInstalled) { + item { + AniFlowCard(modifier = Modifier.fillParentMaxWidth().padding(horizontal = 16.dp).padding(top = 8.dp)) + } + } + item { + DescriptionSection( + component = component, + modifier = Modifier.fillParentMaxWidth().padding(vertical = 8.dp) + ) + } + if (seriesState is SeriesState.Failure) { + item { + ErrorContent( + modifier = Modifier.fillParentMaxWidth() + ) + } + } + items(episodes.toImmutableList(), key = { it.href }) { + EpisodeItem( + item = it, + isLoading = it.isSame(loadingEpisode), + modifier = Modifier.fillParentMaxWidth(), + onClick = component::episode, + onMarkWatched = { c -> + component.watched( + series = (seriesState as SeriesState.Success).series, + combinedEpisode = c + ) + }, + onMarkUnwatched = { c -> + component.unwatched( + series = (seriesState as SeriesState.Success).series, + combinedEpisode = c + ) + }, + onActivate = { c -> + component.activate( + series = (seriesState as SeriesState.Success).series, + episode = c + ) + } + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreenComponent.kt new file mode 100644 index 00000000..ae94609c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/MediumScreenComponent.kt @@ -0,0 +1,316 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import app.cash.sqldelight.coroutines.asFlow +import app.cash.sqldelight.coroutines.mapToList +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.arkivanov.decompose.value.Value +import dev.chrisbanes.haze.HazeState +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.CombinedEpisode +import dev.datlag.burningseries.database.common.combinedEpisodesForSeries +import dev.datlag.burningseries.database.common.episodeForSeries +import dev.datlag.burningseries.database.common.episodeRefreshingData +import dev.datlag.burningseries.database.common.insertEpisodeOrIgnore +import dev.datlag.burningseries.database.common.isFavorite +import dev.datlag.burningseries.database.common.isFavoriteOneShot +import dev.datlag.burningseries.database.common.setEpisodeUnwatched +import dev.datlag.burningseries.database.common.setEpisodeWatched +import dev.datlag.burningseries.database.common.setSeriesFavorite +import dev.datlag.burningseries.database.common.unsetSeriesFavorite +import dev.datlag.burningseries.database.common.updateSeriesData +import dev.datlag.burningseries.database.common.updateSeriesHref +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.network.EpisodeStateMachine +import dev.datlag.burningseries.network.SeriesStateMachine +import dev.datlag.burningseries.network.state.EpisodeAction +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.burningseries.network.state.SeriesState +import dev.datlag.burningseries.other.UserHelper +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.navigation.DialogComponent +import dev.datlag.burningseries.ui.navigation.screen.medium.dialog.activate.ActivateDialogComponent +import dev.datlag.burningseries.ui.navigation.screen.medium.dialog.sponsor.SponsorDialogComponent +import dev.datlag.skeo.DirectLink +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.compose.withMainContext +import dev.datlag.tooling.decompose.ioScope +import dev.datlag.tooling.safeCast +import io.github.aakira.napier.Napier +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn +import org.kodein.di.DI +import org.kodein.di.instance +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.transformLatest + +class MediumScreenComponent( + componentContext: ComponentContext, + override val di: DI, + private val initialSeriesData: SeriesData, + override val initialIsAnime: Boolean, + private val initialLanguage: Language?, + private val onBack: () -> Unit, + private val onWatch: (Series, Series.Episode, ImmutableCollection) -> Unit, + private val onActivate: (Series, Series.Episode) -> Unit +) : MediumComponent, ComponentContext by componentContext { + + private val seriesStateMachine by instance() + override val seriesState: StateFlow = seriesStateMachine.state.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = seriesStateMachine.currentState + ) + + private val successState = seriesState.mapNotNull { + it.safeCast().also { success -> + seriesData = success?.series ?: seriesData + } + } + + override var seriesData: SeriesData = initialSeriesData + private set(value) { + field = value + + database.updateSeriesData(value) + } + + override val seriesTitle: Flow = successState.map { it.series.mainTitle } + override val seriesSubTitle: Flow = successState.map { it.series.subTitle } + override val seriesCover: Flow = successState.map { it.series.coverHref ?: initialSeriesData.coverHref } + override val seriesInfo: Flow> = successState.map { it.series.infoWithoutGenre } + override val seriesSeason: Flow = successState.map { it.series.currentSeason } + override val seriesSeasonList: Flow> = successState.map { it.series.seasons } + override val seriesLanguage: Flow = successState.map { it.series.currentLanguage } + override val seriesLanguageList: Flow> = successState.map { it.series.languages } + override val seriesDescription: Flow = successState.map { it.series.description } + override val seriesIsAnime: Flow = successState.map { it.series.isAnime } + private val episodes: Flow> = successState.map { it.series.episodes } + + private val episodeStateMachine by instance() + override val episodeState: StateFlow = episodeStateMachine.state.flowOn( + context = ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = EpisodeState.None + ) + + private val dialogNavigation = SlotNavigation() + override val dialog: Value> = childSlot( + source = dialogNavigation, + serializer = DialogConfig.serializer() + ) { config, context -> + when (config) { + is DialogConfig.Activate -> ActivateDialogComponent( + componentContext = context, + di = di, + onDismiss = dialogNavigation::dismiss, + onActivate = { + dialogNavigation.dismiss { + onActivate( + config.series, + config.episode + ) + } + } + ) + is DialogConfig.Sponsor -> SponsorDialogComponent( + componentContext = context, + di = di, + onDismiss = { + dialogNavigation.dismiss { + watch( + config.series, + config.episode, + config.streams + ) + } + } + ) + } + } + + private val database by instance() + override val isFavorite: StateFlow = database.isFavorite( + seriesData, + ioDispatcher() + ).stateIn( + scope = ioScope(), + started = SharingStarted.WhileSubscribed(), + initialValue = database.isFavoriteOneShot(seriesData) + ) + + @OptIn(ExperimentalCoroutinesApi::class) + override val combinedEpisodes: Flow> = episodes.transformLatest { collection -> + emit(database.combinedEpisodesForSeries(collection, seriesData)) + + val allFlows = collection.map { database.episodeRefreshingData(it, currentCoroutineContext()) } + emitAll( + combine(allFlows) { all -> + all.toList().toImmutableSet() + } + ) + } + + override val nextCombinedEpisode: Flow = combinedEpisodes.map { list -> + val maxWatchedEpisode = list.mapNotNull { episode -> + if (episode.progress > 0L) { + episode + } else { + null + } + }.maxByOrNull { it.number } + + return@map if (maxWatchedEpisode == null) { + return@map list.minByOrNull { it.number } + } else { + val finished = maxWatchedEpisode.isFinished + + val wantedNumber = if (finished) { + maxWatchedEpisode.number.plus(1) + } else { + return@map maxWatchedEpisode + } + + list.firstOrNull { + it.number == wantedNumber + } + } + } + + private val userHelper by instance() + + init { + val hrefWithLanguage = if (initialLanguage != null) { + seriesData.toHref(newLanguage = initialLanguage.code) + } else { + seriesData.toHref() + } + + seriesStateMachine.href(hrefWithLanguage) + } + + @Composable + override fun render() { + val haze = remember { HazeState() } + + CompositionLocalProvider( + LocalHaze provides haze + ) { + onRenderWithScheme(initialSeriesData) { + MediumScreen(this, it) + } + } + } + + override fun back() { + onBack() + } + + override fun season(value: Series.Season) { + seriesStateMachine.href(seriesData.toHref(newSeason = value.value)) + } + + override fun language(value: Series.Language) { + seriesStateMachine.href(seriesData.toHref(newLanguage = value.value)) + } + + override fun episode(episode: Series.Episode) { + launchIO { + episodeStateMachine.dispatch(EpisodeAction.Load(episode)) + } + } + + private fun watch( + series: Series, + episode: Series.Episode, + streams: ImmutableCollection + ) { + launchIO { + episodeStateMachine.dispatch(EpisodeAction.Clear) + withMainContext { + onWatch(series, episode, streams) + } + } + } + + override fun activate(series: Series, episode: Series.Episode) { + launchIO { + episodeStateMachine.dispatch(EpisodeAction.Clear) + withMainContext { + dialogNavigation.activate(DialogConfig.Activate(series, episode)) + } + } + } + + override fun setFavorite(series: Series) { + database.setSeriesFavorite(series) + } + + override fun unsetFavorite(series: Series) { + database.unsetSeriesFavorite(series) + } + + override fun watched(series: Series, episode: Series.Episode) { + database.insertEpisodeOrIgnore(episode, series) + database.setEpisodeWatched(episode) + } + + override fun unwatched(series: Series, episode: Series.Episode) { + database.insertEpisodeOrIgnore(episode, series) + database.setEpisodeUnwatched(episode) + } + + override fun showSponsoringOrWatch( + series: Series, + episode: Series.Episode, + streams: ImmutableCollection + ) { + val isSponsor = userHelper.isSponsoring + if (isSponsor) { + watch(series, episode, streams) + } else { + launchMain { + if (withIOContext { userHelper.requiresSponsoring() }) { + episodeStateMachine.dispatch(EpisodeAction.Clear) + dialogNavigation.activate( + DialogConfig.Sponsor( + series, episode, streams + ) + ) + } else { + watch(series, episode, streams) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt new file mode 100644 index 00000000..bbfd4e14 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/AniFlowCard.kt @@ -0,0 +1,58 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.other.AniFlow +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource + +@Composable +fun AniFlowCard( + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + + Card( + modifier = modifier, + onClick = { + uriHandler.openUri(AniFlow.googlePlay) + } + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Image( + modifier = Modifier.size(48.dp).clip(MaterialTheme.shapes.medium), + painter = painterResource(AniFlow.icon), + contentDescription = stringResource(AniFlow.title) + ) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = stringResource(AniFlow.title), + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.titleLarge + ) + Text(text = stringResource(AniFlow.subTitle)) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/CoverSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/CoverSection.kt new file mode 100644 index 00000000..078434f4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/CoverSection.kt @@ -0,0 +1,68 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import dev.datlag.burningseries.common.display +import dev.datlag.burningseries.ui.navigation.screen.medium.MediumComponent +import dev.datlag.burningseries.ui.theme.SchemeTheme +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun CoverSection( + component: MediumComponent, + updater: SchemeTheme.Updater?, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + val cover by component.seriesCover.collectAsStateWithLifecycle(component.seriesData.coverHref) + + AsyncImage( + modifier = Modifier + .width(140.dp) + .height(200.dp) + .clip(MaterialTheme.shapes.medium), + model = cover, + contentScale = ContentScale.Crop, + contentDescription = null, + onSuccess = { state -> + updater?.update(state.painter) + } + ) + + Column( + modifier = Modifier.weight(1F).fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically) + ) { + val infoList by component.seriesInfo.collectAsStateWithLifecycle(persistentListOf()) + + infoList.forEach { info -> + info.display( + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt new file mode 100644 index 00000000..683ed7aa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/DescriptionSection.kt @@ -0,0 +1,106 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.animateIntAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ExpandLess +import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.description +import dev.datlag.burningseries.ui.navigation.screen.medium.MediumComponent +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource +import kotlin.math.max + +@Composable +internal fun DescriptionSection( + component: MediumComponent, + modifier: Modifier = Modifier +) { + val description by component.seriesDescription.collectAsStateWithLifecycle(null) + + if (!description.isNullOrBlank()) { + Column( + modifier = modifier.animateContentSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + var descriptionExpandable by remember(description) { mutableStateOf(false) } + var descriptionExpanded by remember(description) { mutableStateOf(false) } + + Row( + modifier = Modifier.fillMaxWidth().padding(top = 16.dp).padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + modifier = Modifier.weight(1F), + text = stringResource(Res.string.description), + style = MaterialTheme.typography.headlineSmall + ) + } + + val animatedLines by animateIntAsState( + targetValue = if (descriptionExpanded) { + Int.MAX_VALUE + } else { + 3 + }, + animationSpec = tween() + ) + + SelectionContainer { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = description!!, + maxLines = max(animatedLines, 1), + softWrap = true, + overflow = TextOverflow.Ellipsis, + onTextLayout = { result -> + if (!descriptionExpanded) { + descriptionExpandable = result.hasVisualOverflow + } + } + ) + } + if (descriptionExpandable) { + IconButton( + modifier = Modifier.fillMaxWidth(), + onClick = { + descriptionExpanded = !descriptionExpanded + } + ) { + val icon = if (descriptionExpanded) { + Icons.Rounded.ExpandLess + } else { + Icons.Rounded.ExpandMore + } + + Icon( + imageVector = icon, + contentDescription = null + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/EpisodeItem.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/EpisodeItem.kt new file mode 100644 index 00000000..2e56a982 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/EpisodeItem.kt @@ -0,0 +1,197 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxHeight +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.material.icons.Icons +import androidx.compose.material.icons.rounded.CheckCircle +import androidx.compose.material.icons.rounded.PauseCircle +import androidx.compose.material.icons.rounded.PlayCircle +import androidx.compose.material3.CardColors +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.materialkolor.ktx.darken +import com.materialkolor.ktx.lighten +import com.vanniktech.blurhash.BlurHash +import dev.datlag.burningseries.LocalDarkMode +import dev.datlag.burningseries.common.decode +import dev.datlag.burningseries.common.toDuration +import dev.datlag.burningseries.database.CombinedEpisode +import dev.datlag.burningseries.model.Series +import dev.datlag.tooling.compose.TopStartCornerShape +import dev.datlag.tooling.compose.onClick +import kotlin.math.roundToInt + +@Composable +internal fun EpisodeItem( + item: CombinedEpisode, + isLoading: Boolean, + modifier: Modifier = Modifier, + onClick: (CombinedEpisode) -> Unit, + onMarkWatched: (CombinedEpisode) -> Unit, + onMarkUnwatched: (CombinedEpisode) -> Unit, + onActivate: (CombinedEpisode) -> Unit +) { + val colors = CardDefaults.elevatedCardColors() + + ElevatedCard( + modifier = modifier + .clip(MaterialTheme.shapes.medium) + .onClick( + enabled = !isLoading && item.hasHoster, + onClick = { + onClick(item) + }, + onDoubleClick = { + if (item.isFinished) { + onMarkUnwatched(item) + } else { + onMarkWatched(item) + } + }, + onLongClick = { + onActivate(item) + } + ), + colors = CardColors( + containerColor = if (item.hasHoster) colors.containerColor else colors.disabledContainerColor, + contentColor = if (item.hasHoster) colors.contentColor else colors.disabledContentColor, + disabledContainerColor = colors.disabledContainerColor, + disabledContentColor = colors.disabledContentColor + ), + elevation = CardDefaults.elevatedCardElevation(defaultElevation = 0.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth().padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier + .height(84.dp) + .aspectRatio(1.75F, true) + .clip(MaterialTheme.shapes.medium), + contentAlignment = Alignment.Center + ) { + val background = if (LocalDarkMode.current) { + LocalContentColor.current.darken(5F) + } else { + LocalContentColor.current.lighten(5F) + } + Box(modifier = Modifier.fillMaxSize().background(background)) + + val imageBitmap = remember(item.blurHash) { + BlurHash.decode( + hash = item.blurHash, + width = 175.dp.value.roundToInt(), + height = 100.dp.value.roundToInt() + ) + } + + imageBitmap?.let { + Image( + modifier = Modifier.matchParentSize(), + bitmap = it, + contentDescription = null + ) + } + + if (item.isFinished) { + Icon( + imageVector = Icons.Rounded.CheckCircle, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } else if (item.isWatching) { + Icon( + imageVector = Icons.Rounded.PauseCircle, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } else { + Icon( + imageVector = Icons.Rounded.PlayCircle, + contentDescription = null + ) + } + if (item.number > 0) { + Text( + modifier = Modifier + .clip( + TopStartCornerShape( + baseShape = MaterialTheme.shapes.medium, + otherCorner = 0.dp + ) + ).background(Color.White) + .align(Alignment.BottomEnd) + .padding(4.dp), + text = item.number.toString(), + color = Color.Black, + fontWeight = FontWeight.SemiBold, + maxLines = 1 + ) + } + if (isLoading) { + CircularProgressIndicator() + } + } + Column( + modifier = Modifier + .defaultMinSize(minHeight = 84.dp) + .fillMaxHeight() + .weight(1F), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = item.mainTitle, + fontWeight = FontWeight.SemiBold, + maxLines = if (item.hasSubtitle) 1 else 2, + softWrap = true, + overflow = TextOverflow.Ellipsis + ) + item.subTitle?.let { sub -> + Text( + text = sub, + maxLines = 1, + softWrap = true, + overflow = TextOverflow.Ellipsis + ) + } + Spacer(modifier = Modifier.weight(1F)) + if (item.length > 0) { + Text( + text = "${item.progress.toDuration()} - ${item.length.toDuration()}", + maxLines = 1, + overflow = TextOverflow.Clip, + style = MaterialTheme.typography.labelSmall + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/SeasonLanguageSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/SeasonLanguageSection.kt new file mode 100644 index 00000000..c404c83f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/SeasonLanguageSection.kt @@ -0,0 +1,194 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +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.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ViewList +import androidx.compose.material.icons.rounded.Translate +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.season_placeholder +import dev.datlag.burningseries.composeapp.generated.resources.select_language +import dev.datlag.burningseries.composeapp.generated.resources.select_season +import dev.datlag.burningseries.other.CountryImage +import dev.datlag.burningseries.ui.navigation.screen.medium.MediumComponent +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.collections.immutable.persistentListOf +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) +@Composable +internal fun SeasonLanguageSection( + component: MediumComponent, + modifier: Modifier = Modifier +) { + FlowRow( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalArrangement = Arrangement.spacedBy(8.dp), + maxItemsInEachRow = 2 + ) { + val selectedSeason by component.seriesSeason.collectAsStateWithLifecycle(null) + val seasonList by component.seriesSeasonList.collectAsStateWithLifecycle(persistentListOf()) + val selectedLanguage by component.seriesLanguage.collectAsStateWithLifecycle(null) + val languageList by component.seriesLanguageList.collectAsStateWithLifecycle(persistentListOf()) + + val seasonDialog = rememberUseCaseState() + val languageDialog = rememberUseCaseState() + + OptionDialog( + state = seasonDialog, + selection = OptionSelection.Single( + options = seasonList.map { season -> + Option( + titleText = season.title.toIntOrNull()?.let { + stringResource(Res.string.season_placeholder, it) + } ?: season.title, + disabled = selectedSeason == season + ) + }, + onSelectOption = { option, _ -> + component.season(seasonList.toList()[option]) + } + ), + config = OptionConfig( + mode = if (seasonList.size > 5) { + DisplayMode.GRID_VERTICAL + } else { + DisplayMode.LIST + } + ), + header = Header.Default( + icon = IconSource( + imageVector = Icons.AutoMirrored.Rounded.ViewList + ), + title = stringResource(Res.string.select_season) + ) + ) + + OptionDialog( + state = languageDialog, + selection = OptionSelection.Single( + options = languageList.map { lang -> + Option( + titleText = lang.title, + disabled = selectedLanguage == lang, + customView = { selected -> + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + val textColor = if (selected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface + } + + Box( + modifier = Modifier.padding(start = 24.dp), + contentAlignment = Alignment.Center + ) { + CountryImage.showFlags( + code = lang.value, + showBorder = true, + iconSize = 24.dp + ) + } + Text( + modifier = Modifier + .padding( + top = 16.dp, + bottom = 16.dp, + start = 16.dp + ) + .fillMaxWidth() + .wrapContentHeight(), + text = lang.title, + maxLines = 1, + style = MaterialTheme.typography.labelLarge, + color = textColor + ) + } + } + ) + }, + onSelectOption = { option, _ -> + component.language(languageList.toList()[option]) + } + ), + config = OptionConfig( + mode = DisplayMode.LIST + ), + header = Header.Default( + icon = IconSource( + imageVector = Icons.Rounded.Translate + ), + title = stringResource(Res.string.select_language) + ) + ) + + AnimatedVisibility( + modifier = Modifier.weight(1F), + visible = selectedSeason != null + ) { + Button( + onClick = { + seasonDialog.show() + }, + enabled = seasonList.size > 1 + ) { + Text( + text = selectedSeason?.title?.toIntOrNull()?.let { + stringResource(Res.string.season_placeholder, it) + } ?: selectedSeason!!.title + ) + } + } + AnimatedVisibility( + modifier = Modifier.weight(1F), + visible = selectedLanguage != null + ) { + Button( + onClick = { + languageDialog.show() + }, + enabled = languageList.size > 1 + ) { + CountryImage.showFlags( + code = selectedLanguage?.value, + iconSize = ButtonDefaults.IconSize, + showBorder = true + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = selectedLanguage!!.title) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/Toolbar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/Toolbar.kt new file mode 100644 index 00000000..431d3e0a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/component/Toolbar.kt @@ -0,0 +1,245 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.TwoWayConverter +import androidx.compose.animation.core.animateIntAsState +import androidx.compose.animation.core.animateValueAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Cast +import androidx.compose.material.icons.rounded.CastConnected +import androidx.compose.material.icons.rounded.Devices +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.FavoriteBorder +import androidx.compose.material.icons.rounded.Speaker +import androidx.compose.material.icons.rounded.Tv +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +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.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.maxkeppeker.sheets.core.models.base.Header +import com.maxkeppeker.sheets.core.models.base.IconSource +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionBody +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.materials.HazeMaterials +import dev.datlag.burningseries.LocalHaze +import dev.datlag.burningseries.common.icon +import dev.datlag.burningseries.common.isConnectedOrConnecting +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.cast +import dev.datlag.burningseries.composeapp.generated.resources.casting_not_supported +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.ui.navigation.screen.medium.MediumComponent +import dev.datlag.kast.ConnectionState +import dev.datlag.kast.DeviceType +import dev.datlag.kast.Kast +import dev.datlag.kast.UnselectReason +import dev.datlag.tooling.Platform +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) +@Composable +internal fun Toolbar( + component: MediumComponent, + series: Series? +) { + TopAppBar( + navigationIcon = { + IconButton( + onClick = { + component.back() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + }, + title = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) + ) { + val seriesTitle by component.seriesTitle.collectAsStateWithLifecycle(component.seriesData.mainTitle) + val seriesSubTitle by component.seriesSubTitle.collectAsStateWithLifecycle(component.seriesData.subTitle) + + Text( + text = seriesTitle, + softWrap = true, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + seriesSubTitle?.ifBlank { null }?.let { sub -> + Text( + text = sub, + softWrap = true, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.labelMedium + ) + } + } + }, + actions = { + val isFavorite by component.isFavorite.collectAsStateWithLifecycle() + + if (!Platform.rememberIsTv()) { + val kastDevices by Kast.allAvailableDevices.collectAsStateWithLifecycle() + val kastState by Kast.connectionState.collectAsStateWithLifecycle() + val kastDialog = rememberUseCaseState() + + OptionDialog( + state = kastDialog, + selection = OptionSelection.Single( + options = kastDevices.map { device -> + Option( + icon = IconSource( + imageVector = when (device.type) { + is DeviceType.TV -> Icons.Rounded.Tv + is DeviceType.SPEAKER -> Icons.Rounded.Speaker + else -> Icons.Rounded.Devices + } + ), + titleText = device.name, + selected = device.isSelected + ) + }, + onSelectOption = { option, _ -> + val device = kastDevices.toList()[option] + + if (device.isSelected) { + Kast.unselect(UnselectReason.disconnected) + } else { + Kast.select(device) + } + } + ), + config = OptionConfig( + mode = DisplayMode.LIST + ), + header = Header.Default( + icon = IconSource( + imageVector = Icons.Rounded.Cast + ), + title = stringResource(Res.string.cast) + ), + body = if (Kast.isSupported) { + null + } else { + OptionBody.Default( + bodyText = stringResource(Res.string.casting_not_supported) + ) + } + ) + + when (kastState) { + is ConnectionState.CONNECTED -> { + IconButton( + onClick = { + Kast.unselect(UnselectReason.disconnected) + } + ) { + Icon( + imageVector = kastState.icon, + contentDescription = null + ) + } + } + is ConnectionState.CONNECTING -> { + IconButton( + onClick = { + Kast.unselect(UnselectReason.disconnected) + } + ) { + Icon( + imageVector = kastState.icon, + contentDescription = null + ) + } + } + else -> { + IconButton( + onClick = { + kastDialog.show() + } + ) { + Icon( + imageVector = kastState.icon, + contentDescription = null + ) + } + } + } + } + + if (isFavorite) { + IconButton( + onClick = { + if (series != null) { + component.unsetFavorite(series) + } + } + ) { + Icon( + imageVector = Icons.Rounded.Favorite, + contentDescription = null, + tint = Color.Red + ) + } + } else { + IconButton( + onClick = { + if (series != null) { + component.setFavorite(series) + } + } + ) { + Icon( + imageVector = Icons.Rounded.FavoriteBorder, + contentDescription = null, + ) + } + } + }, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), + modifier = Modifier.hazeChild( + state = LocalHaze.current, + style = HazeMaterials.thin( + containerColor = MaterialTheme.colorScheme.surface + ) + ).fillMaxWidth() + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateComponent.kt new file mode 100644 index 00000000..180d1fdf --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateComponent.kt @@ -0,0 +1,7 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.activate + +import dev.datlag.burningseries.ui.navigation.DialogComponent + +interface ActivateComponent : DialogComponent { + fun activate() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialog.kt new file mode 100644 index 00000000..7d212300 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialog.kt @@ -0,0 +1,61 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.activate + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Web +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.style.TextOverflow +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.activate +import dev.datlag.burningseries.composeapp.generated.resources.activate_episode_text +import dev.datlag.burningseries.composeapp.generated.resources.activate_episode_title +import dev.datlag.burningseries.composeapp.generated.resources.later +import org.jetbrains.compose.resources.stringResource + +@Composable +fun ActivateDialog(component: ActivateComponent) { + AlertDialog( + onDismissRequest = { + component.dismiss() + }, + icon = { + Icon( + imageVector = Icons.Rounded.Web, + contentDescription = null + ) + }, + title = { + Text( + text = stringResource(Res.string.activate_episode_title), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + softWrap = true + ) + }, + text = { + Text(text = stringResource(Res.string.activate_episode_text)) + }, + confirmButton = { + TextButton( + onClick = { + component.activate() + } + ) { + Text(text = stringResource(Res.string.activate)) + } + }, + dismissButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.later)) + } + } + ) +} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialogComponent.kt similarity index 50% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialogComponent.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialogComponent.kt index c641d8e6..41fdbd97 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/dialog/activate/ActivateDialogComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/activate/ActivateDialogComponent.kt @@ -1,26 +1,19 @@ -package dev.datlag.burningseries.shared.ui.screen.initial.series.dialog.activate +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.activate import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import com.arkivanov.decompose.ComponentContext -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.shared.LocalDI import org.kodein.di.DI class ActivateDialogComponent( componentContext: ComponentContext, override val di: DI, - override val series: Series, - override val episode: Series.Episode, private val onDismiss: () -> Unit, - private val onActivate: (Series, Series.Episode) -> Unit + private val onActivate: () -> Unit ) : ActivateComponent, ComponentContext by componentContext { @Composable override fun render() { - CompositionLocalProvider( - LocalDI provides di - ) { + onRender { ActivateDialog(this) } } @@ -30,7 +23,6 @@ class ActivateDialogComponent( } override fun activate() { - onActivate(series, episode) - dismiss() + onActivate() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorComponent.kt new file mode 100644 index 00000000..62f17d64 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorComponent.kt @@ -0,0 +1,11 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.sponsor + +import dev.datlag.burningseries.ui.navigation.DialogComponent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface SponsorComponent : DialogComponent { + val isLoggedIn: Flow + + fun login() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialog.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialog.kt new file mode 100644 index 00000000..3df08d31 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialog.kt @@ -0,0 +1,100 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.sponsor + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Savings +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.close +import dev.datlag.burningseries.composeapp.generated.resources.sponsor +import dev.datlag.burningseries.composeapp.generated.resources.sponsor_text +import dev.datlag.burningseries.composeapp.generated.resources.sponsor_hint +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.common.rememberIsTv +import dev.datlag.burningseries.composeapp.generated.resources.login +import dev.datlag.tooling.Platform +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle + +@Composable +fun SponsorDialog(component: SponsorComponent) { + val isDesktopOrTv = Platform.isDesktop || Platform.rememberIsTv() + val isLoggedIn by component.isLoggedIn.collectAsStateWithLifecycle(false) + + AlertDialog( + onDismissRequest = {}, + icon = { + Icon( + imageVector = Icons.Rounded.Savings, + contentDescription = null + ) + }, + title = { + Text(text = stringResource(Res.string.sponsor)) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sponsor_text), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.sponsor_hint), + textAlign = TextAlign.Center + ) + } + }, + dismissButton = if (!isDesktopOrTv && !isLoggedIn) { + { + TextButton( + onClick = { + component.login() + } + ) { + Image( + modifier = Modifier.size(ButtonDefaults.IconSize), + painter = painterResource(MokoRes.images.github), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.login)) + } + } + } else null, + confirmButton = { + TextButton( + onClick = { + component.dismiss() + } + ) { + Text(text = stringResource(Res.string.close)) + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialogComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialogComponent.kt new file mode 100644 index 00000000..ac981f24 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/medium/dialog/sponsor/SponsorDialogComponent.kt @@ -0,0 +1,37 @@ +package dev.datlag.burningseries.ui.navigation.screen.medium.dialog.sponsor + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.burningseries.other.UserHelper +import kotlinx.coroutines.flow.map +import org.kodein.di.DI +import org.kodein.di.instance +import org.publicvalue.multiplatform.oidc.flows.CodeAuthFlow + +class SponsorDialogComponent( + componentContext: ComponentContext, + override val di: DI, + private val onDismiss: () -> Unit +) : SponsorComponent, ComponentContext by componentContext { + + private val authFlow by instance() + private val userHelper by instance() + override val isLoggedIn = userHelper.user.map { it != null } + + @Composable + override fun render() { + onRender { + SponsorDialog(this) + } + } + + override fun dismiss() { + onDismiss() + } + + override fun login() { + launchIO { + userHelper.login(authFlow) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoComponent.kt new file mode 100644 index 00000000..af26d496 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoComponent.kt @@ -0,0 +1,27 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import dev.datlag.burningseries.database.Episode +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.burningseries.ui.navigation.Component +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface VideoComponent : Component { + + val series: Series + val episode: Series.Episode + val streams: ImmutableCollection + val startingPos: Long + val startingLength: Long + + val nextEpisode: Flow + + fun back() + fun length(value: Long) + fun progress(value: Long) + fun ended() + fun next(episode: Series.Episode, streams: ImmutableCollection) +} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.kt similarity index 63% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.kt index 27953cdc..ffd67578 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/VideoScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared.ui.screen.video +package dev.datlag.burningseries.ui.navigation.screen.video import androidx.compose.runtime.Composable diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreenComponent.kt new file mode 100644 index 00000000..434e485f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreenComponent.kt @@ -0,0 +1,104 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.runtime.Composable +import app.cash.sqldelight.coroutines.asFlow +import com.arkivanov.decompose.ComponentContext +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.Episode +import dev.datlag.burningseries.database.common.episodeLengthOneShot +import dev.datlag.burningseries.database.common.episodeProgress +import dev.datlag.burningseries.database.common.episodeProgressOneShot +import dev.datlag.burningseries.database.common.insertEpisodeOrIgnore +import dev.datlag.burningseries.database.common.updateLength +import dev.datlag.burningseries.database.common.updateProgress +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.EpisodeStateMachine +import dev.datlag.burningseries.network.state.EpisodeAction +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.skeo.DirectLink +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.compose.withMainContext +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import org.kodein.di.DI +import org.kodein.di.instance +import kotlin.math.max + +class VideoScreenComponent( + componentContext: ComponentContext, + override val di: DI, + override val series: Series, + override val episode: Series.Episode, + override val streams: ImmutableCollection, + private val onBack: () -> Unit, + private val onNext: (Series.Episode, ImmutableCollection) -> Unit +) : VideoComponent, ComponentContext by componentContext { + + private val database by instance() + override val startingPos: Long = max(database.episodeProgressOneShot(episode), 0L) + override val startingLength: Long = max(database.episodeLengthOneShot(episode), 0L) + + private val episodeState by instance() + override val nextEpisode: Flow = episodeState.state.flowOn( + context = ioDispatcher() + ) + + init { + database.insertEpisodeOrIgnore( + episode = episode, + series = series, + ) + } + + override val handlesPIP: Boolean = true + + @Composable + override fun render() { + onRenderWithScheme(series) { + VideoScreen(this) + } + } + + override fun back() { + onBack() + } + + override fun ended() { + var episodeIndex = series.episodes.indexOf(episode) + if (episodeIndex == -1) { + episodeIndex = series.episodes.indexOfFirst { it.href == episode.href } + } + + if (episodeIndex > -1) { + val nextEpisode = series.episodes.toList().getOrNull(episodeIndex + 1) + + if (nextEpisode != null) { + launchIO { + episodeState.dispatch(EpisodeAction.LoadNonSuccess(nextEpisode)) + } + } + } + } + + override fun length(value: Long) { + if (value > 0) { + database.updateLength(value, episode) + } + } + + override fun progress(value: Long) { + if (value > 0) { + database.updateProgress(value, episode) + } + } + + override fun next(episode: Series.Episode, streams: ImmutableCollection) { + launchIO { + episodeState.dispatch(EpisodeAction.Clear) + withMainContext { + onNext(episode, streams) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeComponent.kt new file mode 100644 index 00000000..6eb344ea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeComponent.kt @@ -0,0 +1,9 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome + +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.navigation.Component + +interface WelcomeComponent : Component { + + fun start(language: Language) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreen.kt new file mode 100644 index 00000000..5a1a039c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreen.kt @@ -0,0 +1,148 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.DoubleArrow +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +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.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.app_name +import dev.datlag.burningseries.composeapp.generated.resources.lets_go +import dev.datlag.burningseries.composeapp.generated.resources.welcome_to +import dev.datlag.burningseries.other.AniFlow +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.ui.custom.AndroidFixWindowSize +import dev.datlag.burningseries.ui.navigation.screen.welcome.component.CompactScreen +import dev.datlag.burningseries.ui.navigation.screen.welcome.component.LanguageSelection +import dev.datlag.burningseries.ui.navigation.screen.welcome.component.WideScreen +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +fun WelcomeScreen(component: WelcomeComponent) { + AndroidFixWindowSize { + var selectedLanguage by remember { mutableStateOf(null) } + + when (calculateWindowSizeClass().widthSizeClass) { + WindowWidthSizeClass.Compact -> CompactScreen { + content( + selectedLanguage = selectedLanguage, + onSelect = { + selectedLanguage = it + }, + onStart = { + selectedLanguage?.let { + component.start(it) + } + } + ) + } + else -> WideScreen { + content( + selectedLanguage = selectedLanguage, + onSelect = { + selectedLanguage = it + }, + onStart = { + selectedLanguage?.let { + component.start(it) + } + } + ) + } + } + } +} + +private fun LazyListScope.content( + selectedLanguage: Language?, + onSelect: (Language) -> Unit, + onStart: () -> Unit +) { + item { + Text( + modifier = Modifier.fillParentMaxWidth(0.7F), + text = buildAnnotatedString { + append(stringResource(Res.string.welcome_to)) + append(' ') + withStyle( + SpanStyle( + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold + ) + ) { + append(stringResource(Res.string.app_name)) + } + }, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineMedium + ) + } + item { + LanguageSelection( + modifier = Modifier.fillParentMaxWidth(0.7F), + selected = selectedLanguage, + onSelect = onSelect + ) + } + if (selectedLanguage == Language.JapaneseSubtitle) { + item { + val uriHandler = LocalUriHandler.current + + OutlinedButton( + modifier = Modifier.fillParentMaxWidth(0.7F), + onClick = { + uriHandler.openUri(AniFlow.googlePlay) + } + ) { + Image( + modifier = Modifier.size(ButtonDefaults.IconSize).clip(CircleShape), + painter = painterResource(AniFlow.icon), + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(stringResource(AniFlow.title)) + } + } + } + item { + Button( + onClick = onStart, + modifier = Modifier.fillParentMaxWidth(0.7F), + enabled = selectedLanguage != null + ) { + Text(text = stringResource(Res.string.lets_go)) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.DoubleArrow, + contentDescription = null + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreenComponent.kt new file mode 100644 index 00000000..924ef011 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/WelcomeScreenComponent.kt @@ -0,0 +1,34 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ComponentContext +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.tooling.compose.withMainContext +import org.kodein.di.DI +import org.kodein.di.instance + +class WelcomeScreenComponent( + componentContext: ComponentContext, + override val di: DI, + private val onHome: () -> Unit +) : WelcomeComponent, ComponentContext by componentContext { + + private val settings by instance() + + @Composable + override fun render() { + onRender { + WelcomeScreen(this) + } + } + + override fun start(language: Language) { + launchIO { + settings.setLanguage(language) + withMainContext { + onHome() + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/CompactScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/CompactScreen.kt new file mode 100644 index 00000000..982611f5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/CompactScreen.kt @@ -0,0 +1,45 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.LocalDarkMode +import dev.datlag.burningseries.ui.navigation.screen.welcome.WelcomeComponent +import dev.icerock.moko.resources.compose.painterResource +import dev.datlag.burningseries.MokoRes + +@Composable +internal fun CompactScreen(content: LazyListScope.() -> Unit) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = WindowInsets.safeContent.asPaddingValues() + ) { + item { + val res = if (LocalDarkMode.current) { + MokoRes.images.movie_dark + } else { + MokoRes.images.movie_light + } + + Image( + modifier = Modifier.fillParentMaxWidth(0.5F), + painter = painterResource(res), + contentDescription = null, + contentScale = ContentScale.FillWidth + ) + } + content() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/LanguageSelection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/LanguageSelection.kt new file mode 100644 index 00000000..cc63123e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/LanguageSelection.kt @@ -0,0 +1,107 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome.component + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Info +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.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.Modifier +import androidx.compose.ui.text.style.TextAlign +import dev.datlag.burningseries.common.flags +import dev.datlag.burningseries.common.title +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.select_default_language_text +import dev.datlag.burningseries.other.CountryImage +import dev.datlag.burningseries.settings.model.Language +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LanguageSelection( + selected: Language?, + modifier: Modifier = Modifier, + onSelect: (Language) -> Unit +) { + var exposed by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + modifier = modifier, + expanded = exposed, + onExpandedChange = { exposed = it } + ) { + TextButton( + modifier = Modifier.fillMaxWidth().menuAnchor(), + onClick = { exposed = true } + ) { + CountryImage.showFlags( + collection = selected.flags, + description = stringResource(selected.title), + iconSize = ButtonDefaults.IconSize, + shape = CircleShape + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(selected.title)) + } + + ExposedDropdownMenu( + modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = false), + expanded = exposed, + onDismissRequest = { exposed = false } + ) { + DropdownMenuItem( + enabled = false, + onClick = { }, + text = { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.select_default_language_text), + style = MaterialTheme.typography.labelSmall + ) + }, + leadingIcon = { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.Info, + contentDescription = null + ) + } + ) + Language.all.forEach { lang -> + DropdownMenuItem( + onClick = { + onSelect(lang) + exposed = false + }, + text = { + Text(text = stringResource(lang.title)) + }, + leadingIcon = { + CountryImage.showFlags( + collection = lang.flags, + description = stringResource(lang.title), + iconSize = ButtonDefaults.IconSize, + showBorder = true, + shape = CircleShape + ) + }, + enabled = selected != lang + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/WideScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/WideScreen.kt new file mode 100644 index 00000000..a5135b5e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/welcome/component/WideScreen.kt @@ -0,0 +1,58 @@ +package dev.datlag.burningseries.ui.navigation.screen.welcome.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.LocalDarkMode +import dev.icerock.moko.resources.compose.painterResource +import dev.datlag.burningseries.MokoRes + +@Composable +internal fun WideScreen(content: LazyListScope.() -> Unit) { + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) + ) { + Column( + modifier = Modifier.weight(1F).safeDrawingPadding(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val res = if (LocalDarkMode.current) { + MokoRes.images.movie_dark + } else { + MokoRes.images.movie_light + } + + Image( + modifier = Modifier.fillMaxWidth(0.5F), + painter = painterResource(res), + contentDescription = null, + contentScale = ContentScale.FillWidth + ) + } + LazyColumn( + modifier = Modifier.weight(1F), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = WindowInsets.safeContent.asPaddingValues() + ) { + content() + } + } +} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Colors.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.kt similarity index 57% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Colors.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.kt index 01d3223a..e7e1d759 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/Colors.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.kt @@ -1,40 +1,42 @@ -package dev.datlag.burningseries.shared.ui.theme +package dev.datlag.burningseries.ui.theme +import androidx.compose.material3.ColorScheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -object Colors { +data object Colors { - private const val THEME_LIGHT_PRIMARY = 0xFF0c61a4 + private const val THEME_LIGHT_PRIMARY = 0xFF894a68 private const val THEME_LIGHT_ON_PRIMARY = 0xFFffffff - private const val THEME_LIGHT_PRIMARY_CONTAINER = 0xFFd2e4ff - private const val THEME_LIGHT_ON_PRIMARY_CONTAINER = 0xFF001c37 + private const val THEME_LIGHT_PRIMARY_CONTAINER = 0xFFffd8e7 + private const val THEME_LIGHT_ON_PRIMARY_CONTAINER = 0xFF380723 - private const val THEME_DARK_PRIMARY = 0xFFa0c9ff - private const val THEME_DARK_ON_PRIMARY = 0xFF00325a - private const val THEME_DARK_PRIMARY_CONTAINER = 0xFF00497f - private const val THEME_DARK_ON_PRIMARY_CONTAINER = 0xFFd2e4ff + private const val THEME_DARK_PRIMARY = 0xFFfeb0d2 + private const val THEME_DARK_ON_PRIMARY = 0xFF521d39 + private const val THEME_DARK_PRIMARY_CONTAINER = 0xFF6d3350 + private const val THEME_DARK_ON_PRIMARY_CONTAINER = 0xFFffd8e7 - private const val THEME_LIGHT_SECONDARY = 0xFF535f70 + private const val THEME_LIGHT_SECONDARY = 0xFF725762 private const val THEME_LIGHT_ON_SECONDARY = 0xFFffffff - private const val THEME_LIGHT_SECONDARY_CONTAINER = 0xFFd7e3f8 - private const val THEME_LIGHT_ON_SECONDARY_CONTAINER = 0xFF101c2b + private const val THEME_LIGHT_SECONDARY_CONTAINER = 0xFFfed9e6 + private const val THEME_LIGHT_ON_SECONDARY_CONTAINER = 0xFF2a151f - private const val THEME_DARK_SECONDARY = 0xFFbbc7db - private const val THEME_DARK_ON_SECONDARY = 0xFF253141 - private const val THEME_DARK_SECONDARY_CONTAINER = 0xFF3c4858 - private const val THEME_DARK_ON_SECONDARY_CONTAINER = 0xFFd7e3f8 + private const val THEME_DARK_SECONDARY = 0xFFe0bdca + private const val THEME_DARK_ON_SECONDARY = 0xFF412a34 + private const val THEME_DARK_SECONDARY_CONTAINER = 0xFF59404a + private const val THEME_DARK_ON_SECONDARY_CONTAINER = 0xFFfed9e6 - private const val THEME_LIGHT_TERTIARY = 0xFF006686 - private const val THEME_LIGHT_ON_TERTIARY = 0xFFffffff - private const val THEME_LIGHT_TERTIARY_CONTAINER = 0xFFc0e8ff - private const val THEME_LIGHT_ON_TERTIARY_CONTAINER = 0xFF001e2b + private const val THEME_LIGHT_TERTIARY = 0xFF864B6E + private const val THEME_LIGHT_ON_TERTIARY = 0xFFFFFFFF + private const val THEME_LIGHT_TERTIARY_CONTAINER = 0xFFFFD8EA + private const val THEME_LIGHT_ON_TERTIARY_CONTAINER = 0xFF370728 - private const val THEME_DARK_TERTIARY = 0xFF70d2ff - private const val THEME_DARK_ON_TERTIARY = 0xFF003547 - private const val THEME_DARK_TERTIARY_CONTAINER = 0xFF004d66 - private const val THEME_DARK_ON_TERTIARY_CONTAINER = 0xFFc0e8ff + private const val THEME_DARK_TERTIARY = 0xFFFBB1D8 + private const val THEME_DARK_ON_TERTIARY = 0xFF511D3E + private const val THEME_DARK_TERTIARY_CONTAINER = 0xFF6B3455 + private const val THEME_DARK_ON_TERTIARY_CONTAINER = 0xFFFFD8EA private const val THEME_LIGHT_ERROR = 0xFFba1a1a private const val THEME_LIGHT_ON_ERROR = 0xFFffffff @@ -46,32 +48,31 @@ object Colors { private const val THEME_DARK_ERROR_CONTAINER = 0xFF93000a private const val THEME_DARK_ON_ERROR_CONTAINER = 0xFFffdad6 - private const val THEME_LIGHT_BACKGROUND = 0xFF1a1c1e - private const val THEME_LIGHT_ON_BACKGROUND = 0xFFe3e2e6 + private const val THEME_LIGHT_BACKGROUND = 0xFFFCF8FF + private const val THEME_LIGHT_ON_BACKGROUND = 0xFF1B1B21 - private const val THEME_DARK_BACKGROUND = 0xFF191c1e - private const val THEME_DARK_ON_BACKGROUND = 0xFFe2e2e5 + private const val THEME_DARK_BACKGROUND = 0xFF131318 + private const val THEME_DARK_ON_BACKGROUND = 0xFFE4E1E9 - private const val THEME_LIGHT_SURFACE = 0xFFfdfcff - private const val THEME_LIGHT_ON_SURFACE = 0xFF1a1c1e - private const val THEME_LIGHT_SURFACE_VARIANT = 0xFFdfe2eb - private const val THEME_LIGHT_ON_SURFACE_VARIANT = 0xFF43474e + private const val THEME_LIGHT_SURFACE = 0xFFfff0f4 + private const val THEME_LIGHT_ON_SURFACE = 0xFF21191c + private const val THEME_LIGHT_SURFACE_VARIANT = 0xFFfaeaee + private const val THEME_LIGHT_ON_SURFACE_VARIANT = 0xFF504348 - private const val THEME_DARK_SURFACE = 0xFF1a1c1e - private const val THEME_DARK_ON_SURFACE = 0xFFe3e2e6 - private const val THEME_DARK_SURFACE_VARIANT = 0xFF43474e - private const val THEME_DARK_ON_SURFACE_VARIANT = 0xFFc3c6cf + private const val THEME_DARK_SURFACE = 0xFF21191c + private const val THEME_DARK_ON_SURFACE = 0xFFeedfe3 + private const val THEME_DARK_SURFACE_VARIANT = 0xFF251d20 + private const val THEME_DARK_ON_SURFACE_VARIANT = 0xFFd4c2c7 - private const val THEME_LIGHT_OUTLINE = 0xFF73777f - private const val THEME_LIGHT_INVERSE_SURFACE = 0xFF2e3133 - private const val THEME_LIGHT_INVERSE_ON_SURFACE = 0xFFf0f0f3 - private const val THEME_LIGHT_INVERSE_PRIMARY = 0xFF88ceff - - private const val THEME_DARK_OUTLINE = 0xFF8d9199 - private const val THEME_DARK_INVERSE_SURFACE = 0xFFe2e2e5 - private const val THEME_DARK_INVERSE_ON_SURFACE = 0xFF2e3133 - private const val THEME_DARK_INVERSE_PRIMARY = 0xFF006590 + private const val THEME_LIGHT_OUTLINE = 0xFF827378 + private const val THEME_LIGHT_INVERSE_SURFACE = 0xFF372e31 + private const val THEME_LIGHT_INVERSE_ON_SURFACE = 0xFFfdedf1 + private const val THEME_LIGHT_INVERSE_PRIMARY = 0xFFfeb0d2 + private const val THEME_DARK_OUTLINE = 0xFF9d8d92 + private const val THEME_DARK_INVERSE_SURFACE = 0xFFeedfe3 + private const val THEME_DARK_INVERSE_ON_SURFACE = 0xFF372e31 + private const val THEME_DARK_INVERSE_PRIMARY = 0xFF894a68 fun getDarkScheme() = darkColorScheme( primary = Color(THEME_DARK_PRIMARY), @@ -142,5 +143,10 @@ object Colors { inverseOnSurface = Color(THEME_LIGHT_INVERSE_ON_SURFACE), inversePrimary = Color(THEME_LIGHT_INVERSE_PRIMARY) ) +} + +@Composable +expect fun Colors.dynamicDark(): ColorScheme -} \ No newline at end of file +@Composable +expect fun Colors.dynamicLight(): ColorScheme \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/DynamicMaterialTheme.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/DynamicMaterialTheme.kt similarity index 97% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/DynamicMaterialTheme.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/DynamicMaterialTheme.kt index c19aad03..62babacc 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/DynamicMaterialTheme.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/DynamicMaterialTheme.kt @@ -1,4 +1,4 @@ -package dev.datlag.burningseries.shared.ui.theme +package dev.datlag.burningseries.ui.theme import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.AnimationSpec @@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.Color import com.materialkolor.Contrast import com.materialkolor.PaletteStyle import com.materialkolor.rememberDynamicColorScheme -import dev.datlag.burningseries.shared.LocalDarkMode +import dev.datlag.burningseries.LocalDarkMode @Composable fun DynamicMaterialTheme( diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.kt b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/SchemeTheme.kt similarity index 57% rename from app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.kt rename to composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/SchemeTheme.kt index 7c950b69..61dadd90 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/theme/SchemeTheme.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/burningseries/ui/theme/SchemeTheme.kt @@ -1,16 +1,11 @@ -package dev.datlag.burningseries.shared.ui.theme +package dev.datlag.burningseries.ui.theme -import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.Painter import com.kmpalette.DominantColorState import com.kmpalette.palette.graphics.Palette @@ -18,40 +13,39 @@ import com.kmpalette.rememberPainterDominantColorState import com.materialkolor.DynamicMaterialTheme import com.mayakapps.kache.InMemoryKache import com.mayakapps.kache.KacheStrategy -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.shared.LocalDarkMode -import dev.datlag.burningseries.shared.common.ioDispatcher -import dev.datlag.burningseries.shared.common.launchIO -import dev.datlag.burningseries.shared.common.lifecycle.collectAsStateWithLifecycle -import dev.datlag.burningseries.shared.common.withIOContext +import dev.datlag.burningseries.LocalDI +import dev.datlag.burningseries.common.plainOnColor +import dev.datlag.burningseries.model.SeriesData +import dev.datlag.burningseries.model.coroutines.Executor +import dev.datlag.burningseries.settings.Settings +import dev.datlag.tooling.async.scopeCatching +import dev.datlag.tooling.async.suspendCatching +import dev.datlag.tooling.compose.ioDispatcher +import dev.datlag.tooling.compose.launchDefault +import dev.datlag.tooling.compose.launchIO +import dev.datlag.tooling.compose.toTypography +import dev.datlag.tooling.compose.withIOContext +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import io.github.alexzhirkevich.qrose.toByteArray import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* +import org.kodein.di.instance import kotlin.coroutines.CoroutineContext - -val DominantColorState?.primary - @Composable - get() = this?.color ?: MaterialTheme.colorScheme.primary - -val DominantColorState?.onPrimary - @Composable - get() = this?.onColor ?: MaterialTheme.colorScheme.onPrimary - -val Color.plainOnColor: Color - get() = if (this.luminance() > 0.5F) { - Color.Black - } else { - Color.White - } +import kotlin.math.roundToInt data object SchemeTheme { - internal val commonSchemeKey = MutableStateFlow(null) + internal val executor = Executor() private val kache = InMemoryKache>( maxSize = 25L * 1024 * 1024 ) { strategy = KacheStrategy.LRU } + private val imageBytes = InMemoryKache( + maxSize = 5L * 1024 * 1024 + ) { + strategy = KacheStrategy.LRU + } internal fun get(key: Any) = scopeCatching { kache.getIfAvailable(key) @@ -65,6 +59,53 @@ data object SchemeTheme { kache.getOrPut(key) { fallback } }.getOrNull() + internal fun getByteArray(key: Any) = scopeCatching { + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } + + imageBytes.getIfAvailable(usingKey) + }.getOrNull() + + internal suspend fun getOrPutByteArray(key: Any, fallback: ByteArray = ByteArray(0)): ByteArray? { + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } + + return suspendCatching { + imageBytes.getIfAvailable(usingKey) + }.getOrNull() ?: suspendCatching { + if (fallback.isNotEmpty()) { + imageBytes.put(usingKey, fallback) + } else { + null + } + }.getOrNull() ?: suspendCatching { + imageBytes.getOrPut(usingKey) { + if (fallback.isNotEmpty()) { + fallback + } else { + null + } + } + }.getOrNull() + } + + internal suspend fun setByteArray(key: Any, data: ByteArray) = suspendCatching { + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } + + if (data.isNotEmpty()) { + imageBytes.put(usingKey, data) + } else { + null + } + }.isSuccess + @Composable fun create( key: Any?, @@ -85,18 +126,16 @@ data object SchemeTheme { ) val scope = rememberCoroutineScope() return remember(state, scope) { - state?.let { Updater.State(scope, it) } + state?.let { Updater.State(key, scope, it) } } } - fun setCommon(key: Any?) { - commonSchemeKey.update { key } - } - sealed interface Updater { fun update(input: Painter?) + fun update(color: Color?) = update(color?.let(::ColorPainter)) data class State( + private val key: Any, private val scope: CoroutineScope, private val state: DominantColorState ) : Updater { @@ -106,7 +145,14 @@ data object SchemeTheme { } scope.launchIO { - state.updateFrom(input) + executor.enqueue { + setByteArray(key, input.toByteArray( + width = input.intrinsicSize.width.roundToInt(), + height = input.intrinsicSize.height.roundToInt() + )) + + state.updateFrom(input) + } } } } @@ -121,8 +167,15 @@ data object SchemeTheme { } scope.launchIO { - val state = get(key) ?: return@launchIO - state.updateFrom(input) + executor.enqueue { + setByteArray(key, input.toByteArray( + width = input.intrinsicSize.width.roundToInt(), + height = input.intrinsicSize.height.roundToInt() + )) + + val state = get(key) ?: return@enqueue + state.updateFrom(input) + } } } } @@ -141,9 +194,14 @@ fun rememberSchemeThemeDominantColorState( return null } - val existingState = remember(key) { - SchemeTheme.get(key) - } ?: SchemeTheme.get(key) + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } + + val existingState = remember(usingKey) { + SchemeTheme.get(usingKey) + } ?: SchemeTheme.get(usingKey) if (existingState != null) { return existingState @@ -156,9 +214,9 @@ fun rememberSchemeThemeDominantColorState( isSwatchValid = isSwatchValid, coroutineContext = ioDispatcher() ) - val state by produceState?>(null, key) { + val state by produceState?>(null, usingKey) { value = withIOContext { - SchemeTheme.getOrPut(key, fallbackState) ?: fallbackState + SchemeTheme.getOrPut(usingKey, fallbackState) ?: fallbackState } } @@ -174,8 +232,13 @@ fun rememberSchemeThemeDominantColorState( applyMinContrast: Boolean = false, minContrastBackgroundColor: Color = Color.Transparent ): DominantColorState? { + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } + return rememberSchemeThemeDominantColorState( - key = key, + key = usingKey, defaultColor = defaultColor, defaultOnColor = defaultOnColor, builder = { @@ -198,7 +261,7 @@ fun rememberSchemeThemeDominantColorState( @Composable fun SchemeTheme( key: Any?, - animate: Boolean = true, + animate: Boolean = false, defaultColor: Color? = null, defaultOnColor: Color? = null, content: @Composable (SchemeTheme.Updater?) -> Unit @@ -206,12 +269,17 @@ fun SchemeTheme( val onColor = defaultOnColor ?: remember(defaultColor) { defaultColor?.plainOnColor } + val usingKey = when (key) { + is SeriesData -> key.source + else -> key + } val state = rememberSchemeThemeDominantColorState( - key = key, + key = usingKey, defaultColor = defaultColor ?: MaterialTheme.colorScheme.primary, defaultOnColor = onColor ?: MaterialTheme.colorScheme.onPrimary, ) - val updater = SchemeTheme.create(key) + val updater = SchemeTheme.create(usingKey) + val appSettings by LocalDI.current.instance() DynamicMaterialTheme( seedColor = state?.color, @@ -221,23 +289,6 @@ fun SchemeTheme( } } -@Composable -fun CommonSchemeTheme( - animate: Boolean = true, - content: @Composable (SchemeTheme.Updater?) -> Unit -) { - val key by SchemeTheme.commonSchemeKey.collectAsStateWithLifecycle() - - SchemeTheme( - key = key, - animate = animate, - content = content - ) -} - -@Composable -expect fun SchemeThemeSystemProvider(scheme: ColorScheme, content: @Composable () -> Unit) - private fun Color.contrastAgainst(background: Color): Float { val fg = if (alpha < 1f) compositeOver(background) else this diff --git a/app/shared/src/commonMain/resources/MR/images/AniFlow.svg b/composeApp/src/commonMain/moko-resources/images/AniFlow.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/AniFlow.svg rename to composeApp/src/commonMain/moko-resources/images/AniFlow.svg diff --git a/composeApp/src/commonMain/moko-resources/images/AppIcon.svg b/composeApp/src/commonMain/moko-resources/images/AppIcon.svg new file mode 100644 index 00000000..f8f1d861 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/AppIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/shared/src/commonMain/resources/MR/images/COUNTRY_UNKNOWN.svg b/composeApp/src/commonMain/moko-resources/images/COUNTRY_UNKNOWN.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/COUNTRY_UNKNOWN.svg rename to composeApp/src/commonMain/moko-resources/images/COUNTRY_UNKNOWN.svg diff --git a/app/shared/src/commonMain/resources/MR/images/DE.svg b/composeApp/src/commonMain/moko-resources/images/DE.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/DE.svg rename to composeApp/src/commonMain/moko-resources/images/DE.svg diff --git a/app/shared/src/commonMain/resources/MR/images/JP.svg b/composeApp/src/commonMain/moko-resources/images/JP.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/JP.svg rename to composeApp/src/commonMain/moko-resources/images/JP.svg diff --git a/composeApp/src/commonMain/moko-resources/images/Logo.svg b/composeApp/src/commonMain/moko-resources/images/Logo.svg new file mode 100644 index 00000000..936dfbf7 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/Logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/shared/src/commonMain/resources/MR/images/US.svg b/composeApp/src/commonMain/moko-resources/images/US.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/US.svg rename to composeApp/src/commonMain/moko-resources/images/US.svg diff --git a/composeApp/src/commonMain/moko-resources/images/banner/background.svg b/composeApp/src/commonMain/moko-resources/images/banner/background.svg new file mode 100644 index 00000000..b74bd1cc --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/banner/background.svg @@ -0,0 +1,3 @@ + + + diff --git a/composeApp/src/commonMain/moko-resources/images/banner/girl_floating@1x.png b/composeApp/src/commonMain/moko-resources/images/banner/girl_floating@1x.png new file mode 100644 index 00000000..664d5909 Binary files /dev/null and b/composeApp/src/commonMain/moko-resources/images/banner/girl_floating@1x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/banner/lighting.svg b/composeApp/src/commonMain/moko-resources/images/banner/lighting.svg new file mode 100644 index 00000000..66f7463b --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/banner/lighting.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/composeApp/src/commonMain/moko-resources/images/banner/title@1x.png b/composeApp/src/commonMain/moko-resources/images/banner/title@1x.png new file mode 100644 index 00000000..50515e37 Binary files /dev/null and b/composeApp/src/commonMain/moko-resources/images/banner/title@1x.png differ diff --git a/composeApp/src/commonMain/moko-resources/images/donut_dark.svg b/composeApp/src/commonMain/moko-resources/images/donut_dark.svg new file mode 100644 index 00000000..9dcdc2ee --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/donut_dark.svg @@ -0,0 +1 @@ +donut_love \ No newline at end of file diff --git a/composeApp/src/commonMain/moko-resources/images/donut_light.svg b/composeApp/src/commonMain/moko-resources/images/donut_light.svg new file mode 100644 index 00000000..7430f8d3 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/donut_light.svg @@ -0,0 +1 @@ +donut_love \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/GitHub.svg b/composeApp/src/commonMain/moko-resources/images/github.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/GitHub.svg rename to composeApp/src/commonMain/moko-resources/images/github.svg diff --git a/composeApp/src/commonMain/moko-resources/images/movie_dark.svg b/composeApp/src/commonMain/moko-resources/images/movie_dark.svg new file mode 100644 index 00000000..65d72d96 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/movie_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/composeApp/src/commonMain/moko-resources/images/movie_light.svg b/composeApp/src/commonMain/moko-resources/images/movie_light.svg new file mode 100644 index 00000000..4c72b99d --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/movie_light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/shared/src/commonMain/resources/MR/images/Patreon.svg b/composeApp/src/commonMain/moko-resources/images/patreon.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/Patreon.svg rename to composeApp/src/commonMain/moko-resources/images/patreon.svg diff --git a/app/shared/src/commonMain/resources/MR/images/Polar.svg b/composeApp/src/commonMain/moko-resources/images/polar.svg similarity index 100% rename from app/shared/src/commonMain/resources/MR/images/Polar.svg rename to composeApp/src/commonMain/moko-resources/images/polar.svg diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt new file mode 100644 index 00000000..1813d86e --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/Main.kt @@ -0,0 +1,166 @@ +package dev.datlag.burningseries + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.window.WindowDraggableArea +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.ComposeWindow +import androidx.compose.ui.window.WindowState +import androidx.compose.ui.window.singleWindowApplication +import coil3.ImageLoader +import coil3.SingletonImageLoader +import coil3.annotation.DelicateCoilApi +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.lifecycle.LifecycleController +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.PredictiveBackGestureIcon +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.PredictiveBackGestureOverlay +import com.arkivanov.essenty.backhandler.BackDispatcher +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.arkivanov.essenty.lifecycle.LifecycleOwner +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import dev.datlag.burningseries.common.nullableFirebaseInstance +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.app_name +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.firebase.initializePlatform +import dev.datlag.burningseries.module.NetworkModule +import dev.datlag.burningseries.other.StateSaver +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.ui.navigation.RootComponent +import dev.datlag.kast.Kast +import dev.datlag.sekret.NativeLoader +import dev.datlag.tooling.Tooling +import dev.datlag.tooling.applicationTitle +import dev.datlag.tooling.decompose.lifecycle.LocalLifecycleOwner +import dev.datlag.tooling.scopeCatching +import dev.datlag.tooling.systemProperty +import io.github.aakira.napier.DebugAntilog +import io.github.aakira.napier.Napier +import kotlinx.coroutines.runBlocking +import org.jetbrains.compose.resources.getString +import org.kodein.di.DI +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import javax.swing.SwingUtilities +import java.io.File + +val LocalWindow = staticCompositionLocalOf { null } + +fun main(vararg args: String) { + Napier.base(DebugAntilog()) + StateSaver.sekretLibraryLoaded = NativeLoader.loadLibrary( + name = "sekret", + path = systemProperty("compose.application.resources.dir")?.let(::File) + ) + FirebaseFactory.initializePlatform() + Kast.restartDiscovery() + + val di = DI { + import(NetworkModule.di) + } + val firebase = di.nullableFirebaseInstance()?.auth + + Runtime.getRuntime().addShutdownHook(Thread { + Kast.dispose() + + runBlocking { + firebase?.delete() + } + }) + + runWindow(di) + + Kast.dispose() + runBlocking { + firebase?.delete() + } +} + +@OptIn(ExperimentalDecomposeApi::class, DelicateCoilApi::class) +private fun runWindow(di: DI) { + val appTitle = runBlocking { + getString(Res.string.app_name) + } + Tooling.applicationTitle(appTitle) + + val imageLoader by di.instance() + SingletonImageLoader.setUnsafe(imageLoader) + + val windowState = WindowState() + val lifecycle = LifecycleRegistry() + val lifecycleOwner = object : LifecycleOwner { + override val lifecycle: Lifecycle = lifecycle + } + val backDispatcher = BackDispatcher() + val root = runOnUiThread { + RootComponent( + componentContext = DefaultComponentContext( + lifecycle = lifecycle, + backHandler = backDispatcher + ), + di = di + ) + } + + singleWindowApplication( + state = windowState, + title = appTitle, + exitProcessOnExit = true + ) { + LifecycleController(lifecycle, windowState) + + val appSettings by di.instance() + LaunchedEffect(Unit) { + appSettings.increaseStartCounter() + } + + CompositionLocalProvider( + LocalLifecycleOwner provides lifecycleOwner, + LocalWindow provides window + ) { + App(di) { + PredictiveBackGestureOverlay( + backDispatcher = backDispatcher, + backIcon = { progress, _ -> + PredictiveBackGestureIcon( + imageVector = Icons.Rounded.ArrowBackIosNew, + progress = progress, + iconTintColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundColor = MaterialTheme.colorScheme.secondaryContainer + ) + }, + modifier = Modifier.fillMaxSize() + ) { + root.render() + } + } + } + } +} + +@Suppress("UNCHECKED_CAST") +private fun runOnUiThread(block: () -> T): T { + if (SwingUtilities.isEventDispatchThread()) { + return block() + } + + var error: Throwable? = null + var result: T? = null + + SwingUtilities.invokeAndWait { + val res = scopeCatching(block) + error = res.exceptionOrNull() + result = res.getOrNull() + } + + error?.also { throw it } + + return result as T +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.jvm.kt new file mode 100644 index 00000000..d00a3433 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/common/PlatformExtend.jvm.kt @@ -0,0 +1,70 @@ +package dev.datlag.burningseries.common + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.toComposeImageBitmap +import com.vanniktech.blurhash.BlurHash +import dev.datlag.tooling.Platform +import dev.datlag.tooling.scopeCatching +import dev.datlag.tooling.systemEnv +import dev.datlag.tooling.systemProperty +import java.net.InetAddress + +actual fun BlurHash.decode( + hash: String?, + width: Int, + height: Int +): ImageBitmap? { + if (hash.isNullOrBlank()) { + return null + } + + val image = decode( + blurHash = hash, + width = width, + height = height + ) + return image?.toComposeImageBitmap() +} + +@Composable +actual fun Modifier.drawProgress(color: Color, progress: Float): Modifier = drawWithContent { + with(drawContext.canvas.nativeCanvas) { + val checkPoint = saveLayer(null, null) + + drawContent() + + drawRect( + color = color, + size = Size(size.width * progress, size.height), + blendMode = BlendMode.SrcOut + ) + + restoreToCount(checkPoint) + } +} + +@Composable +actual fun Platform.rememberIsTv(): Boolean { + return false +} + +fun Platform.deviceName(): String { + return systemEnv("COMPUTERNAME")?.ifBlank { null } + ?: systemEnv("HOSTNAME")?.ifBlank { null } + ?: systemProperty("COMPUTERNAME")?.ifBlank { null } + ?: systemProperty("HOSTNAME")?.ifBlank { null } + ?: scopeCatching { + InetAddress.getLocalHost().hostName + }.getOrNull()?.ifBlank { null } + ?: scopeCatching { + InetAddress.getLocalHost().canonicalHostName + }.getOrNull()?.ifBlank { null } + ?: "Desktop" +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/module/PlatformModule.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/module/PlatformModule.jvm.kt new file mode 100644 index 00000000..1faf3e5a --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/module/PlatformModule.jvm.kt @@ -0,0 +1,181 @@ +package dev.datlag.burningseries.module + +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.okio.OkioStorage +import coil3.ImageLoader +import coil3.PlatformContext +import dev.datlag.burningseries.BuildKonfig +import dev.datlag.burningseries.Sekret +import dev.datlag.burningseries.common.deviceName +import dev.datlag.burningseries.database.DriverFactory +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.firebase.initialize +import dev.datlag.burningseries.other.StateSaver +import dev.datlag.burningseries.settings.DataStoreAppSettings +import dev.datlag.burningseries.settings.DataStoreUserSettings +import dev.datlag.burningseries.settings.Settings +import dev.datlag.burningseries.settings.model.AppSettings +import dev.datlag.burningseries.settings.model.UserSettings +import dev.datlag.tooling.Platform +import dev.datlag.tooling.Tooling +import dev.datlag.tooling.createAsFileSafely +import dev.datlag.tooling.getRWUserConfigFile +import dev.datlag.tooling.getRWUserDataFile +import dev.datlag.tooling.systemProperty +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.json.json +import okhttp3.Dns +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.dnsoverhttps.DnsOverHttps +import okio.FileSystem +import okio.Path.Companion.toOkioPath +import org.kodein.di.DI +import org.kodein.di.bindEagerSingleton +import org.kodein.di.bindSingleton +import org.kodein.di.instance +import org.publicvalue.multiplatform.oidc.appsupport.CodeAuthFlowFactory +import org.publicvalue.multiplatform.oidc.appsupport.JvmCodeAuthFlowFactory +import java.net.InetAddress +import java.util.concurrent.TimeUnit + +actual object PlatformModule { + + private const val NAME = "DesktopPlatformModule" + private const val APP_NAME = "Burning-Series" + private const val APP_VERSION = "APP_VERSION" + private const val DEVICE_NAME = "DEVICE_NAME" + + actual val di: DI.Module = DI.Module(NAME) { + bindSingleton { + PlatformContext.INSTANCE + } + bindSingleton { + OkHttpClient.Builder() + .followRedirects(true) + .followSslRedirects(true) + .connectTimeout(3, TimeUnit.MINUTES) + .readTimeout(3, TimeUnit.MINUTES) + .writeTimeout(3, TimeUnit.MINUTES) + .build() + } + bindSingleton { + DnsOverHttps.Builder() + .client(instance()) + .url("https://dns.google/dns-query".toHttpUrl()) + .bootstrapDnsHosts(InetAddress.getByName("8.8.4.4"), InetAddress.getByName("8.8.8.8")) + .build() + } + bindSingleton { + HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + connectTimeout(3, TimeUnit.MINUTES) + readTimeout(3, TimeUnit.MINUTES) + writeTimeout(3, TimeUnit.MINUTES) + dns(instance()) + } + } + install(ContentNegotiation) { + json(instance(), ContentType.Application.Json) + json(instance(), ContentType.Text.Plain) + } + } + } + bindSingleton("STREAM_CLIENT") { + HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + } + } + } + } + bindSingleton> { + DataStoreFactory.create( + storage = OkioStorage( + fileSystem = FileSystem.SYSTEM, + serializer = AppSettings.SettingsSerializer, + producePath = { + Tooling.getRWUserConfigFile( + child = "app.settings", + appName = APP_NAME, + appVersion = "v6" + ).also { it.createAsFileSafely() }.toOkioPath() + } + ) + ) + } + bindSingleton { + DataStoreAppSettings(instance()) + } + bindSingleton> { + DataStoreFactory.create( + storage = OkioStorage( + fileSystem = FileSystem.SYSTEM, + serializer = UserSettings.SettingsSerializer, + producePath = { + Tooling.getRWUserConfigFile( + child = "user.settings", + appName = APP_NAME, + appVersion = "v6" + ).also { it.createAsFileSafely() }.toOkioPath() + } + ) + ) + } + bindSingleton { + DataStoreUserSettings(instance()) + } + bindSingleton { + if (StateSaver.sekretLibraryLoaded) { + FirebaseFactory.initialize( + projectId = Sekret.firebaseProject(BuildKonfig.packageName), + applicationId = Sekret.firebaseApplication(BuildKonfig.packageName)!!, + apiKey = Sekret.firebaseApiKey(BuildKonfig.packageName)!!, + localLogger = object : FirebaseFactory.Crashlytics.LocalLogger { + override fun warn(message: String?) { + message?.let { Napier.w(it) } + } + + override fun error(message: String?) { + message?.let { Napier.e(it) } + } + + override fun error(throwable: Throwable?) { + throwable?.let { Napier.e("", it) } + } + } + ) + } else { + FirebaseFactory.Empty + } + } + bindSingleton { + DriverFactory( + file = Tooling.getRWUserDataFile( + child = "bs.db", + appName = APP_NAME, + appVersion = "v6" + ) + ) + } + bindSingleton { + JvmCodeAuthFlowFactory() + } + systemProperty("jpackage.app-version")?.let { + bindSingleton(APP_VERSION) { it } + } + bindSingleton(DEVICE_NAME) { + Platform.deviceName() + } + } +} + +actual fun ImageLoader.Builder.extendImageLoader(): ImageLoader.Builder = this \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/other/AniFlow.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/other/AniFlow.jvm.kt new file mode 100644 index 00000000..0a7fcc9c --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/other/AniFlow.jvm.kt @@ -0,0 +1,8 @@ +package dev.datlag.burningseries.other + +import androidx.compose.runtime.Composable + +@Composable +actual fun AniFlow.isInstalled(): Boolean { + return false +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.jvm.kt new file mode 100644 index 00000000..61b8e765 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/Scrollbar.jvm.kt @@ -0,0 +1,80 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.foundation.LocalScrollbarStyle +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier + +@Composable +actual fun VerticalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier, + style: ScrollbarStyle +) { + androidx.compose.foundation.VerticalScrollbar( + adapter = adapter, + modifier = modifier, + style = androidx.compose.foundation.ScrollbarStyle( + minimalHeight = style.minimalHeight, + thickness = style.thickness, + shape = style.shape, + hoverDurationMillis = style.hoverDurationMillis, + unhoverColor = style.unhoverColor, + hoverColor = style.hoverColor + ) + ) +} + +@Composable +actual fun HorizontalScrollbar( + adapter: ScrollbarAdapter, + modifier: Modifier, + style: ScrollbarStyle +) { + androidx.compose.foundation.HorizontalScrollbar( + adapter = adapter, + modifier = modifier, + style = androidx.compose.foundation.ScrollbarStyle( + minimalHeight = style.minimalHeight, + thickness = style.thickness, + shape = style.shape, + hoverDurationMillis = style.hoverDurationMillis, + unhoverColor = style.unhoverColor, + hoverColor = style.hoverColor + ) + ) +} + +@Composable +actual fun rememberScrollbarAdapter( + scrollState: LazyGridState, +): ScrollbarAdapter = remember(scrollState) { + androidx.compose.foundation.ScrollbarAdapter(scrollState) +} + +@Composable +actual fun rememberScrollbarAdapter( + scrollState: LazyListState +): ScrollbarAdapter = remember(scrollState) { + androidx.compose.foundation.ScrollbarAdapter(scrollState) +} + +actual typealias ScrollbarAdapter = androidx.compose.foundation.v2.ScrollbarAdapter + +@Composable +actual fun localScrollbarStyle(): ScrollbarStyle { + val current = LocalScrollbarStyle.current + + return remember { + ScrollbarStyle( + minimalHeight = current.minimalHeight, + thickness = current.thickness, + shape = current.shape, + hoverDurationMillis = current.hoverDurationMillis, + unhoverColor = current.unhoverColor, + hoverColor = current.hoverColor + ) + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.jvm.kt new file mode 100644 index 00000000..de5a59f0 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/custom/WindowSize.jvm.kt @@ -0,0 +1,8 @@ +package dev.datlag.burningseries.ui.custom + +import androidx.compose.runtime.Composable + +@Composable +actual fun AndroidFixWindowSize(content: @Composable () -> Unit) { + content() +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.jvm.kt new file mode 100644 index 00000000..9cfd02f1 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/activate/ActivateScreen.jvm.kt @@ -0,0 +1,115 @@ +package dev.datlag.burningseries.ui.navigation.screen.activate + +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.AdsClick +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Download +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.activate_on_desktop_1 +import dev.datlag.burningseries.composeapp.generated.resources.activate_on_desktop_2 +import dev.datlag.burningseries.composeapp.generated.resources.download +import dev.datlag.burningseries.composeapp.generated.resources.open_in_browser +import dev.datlag.burningseries.model.BSUtil +import dev.datlag.burningseries.other.Constants +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +actual fun ActivateScreen(component: ActivateComponent) { + Scaffold( + topBar = { + TopAppBar( + navigationIcon = { + IconButton( + onClick = { + component.back() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + }, + title = { + Text(text = component.episode.mainTitle) + } + ) + } + ) { padding -> + Column( + modifier = Modifier.fillMaxSize().padding(padding), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7F), + text = stringResource(Res.string.activate_on_desktop_1), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7F), + text = stringResource(Res.string.activate_on_desktop_2), + textAlign = TextAlign.Center + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + val uriHandler = LocalUriHandler.current + + OutlinedButton( + onClick = { + uriHandler.openUri(Constants.GITHUB_RELEASE) + } + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.Download, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.download)) + } + Button( + onClick = { + uriHandler.openUri(BSUtil.getBurningSeriesLink(component.episode.href)) + } + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.AdsClick, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.open_in_browser)) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.jvm.kt new file mode 100644 index 00000000..cfc4f2c1 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/AniFlowIconButton.jvm.kt @@ -0,0 +1,19 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.material.IconButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable + +@Composable +actual fun AniFlowIconButton(onClick: () -> Unit) { + IconButton( + onClick = onClick + ) { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.jvm.kt new file mode 100644 index 00000000..40afb531 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/component/LinkSupportSection.jvm.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyListScope +import dev.datlag.tooling.Platform +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +actual val Platform.linksSupported: StateFlow + get() = MutableStateFlow(false) + +actual fun LazyListScope.LinkSupportSection(headerPadding: PaddingValues) { } \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.jvm.kt new file mode 100644 index 00000000..8bb41d5d --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/release/ReleaseDialog.jvm.kt @@ -0,0 +1,100 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.release + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.BrowserUpdated +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.ColorFilter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.MokoRes +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.release_text_1 +import dev.datlag.burningseries.composeapp.generated.resources.release_text_2 +import dev.datlag.burningseries.composeapp.generated.resources.view +import dev.datlag.burningseries.other.Constants +import dev.icerock.moko.resources.compose.painterResource +import org.jetbrains.compose.resources.stringResource + +@Composable +actual fun ReleaseDialog(component: ReleaseComponent) { + val isDraftOrPreRelease = remember(component.release) { component.release.isDraft || component.release.isPrerelease } + var dismissRequests by remember(isDraftOrPreRelease) { mutableIntStateOf(if (isDraftOrPreRelease) 5 else 0) } + + AlertDialog( + onDismissRequest = { + dismissRequests++ + if (dismissRequests >= 5) { + component.dismiss() + } + }, + icon = { + Icon( + imageVector = DeviceIcon, + contentDescription = null + ) + }, + title = { + Text(text = component.release.title ?: component.release.tagName) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.release_text_1, component.release.tagName), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(Res.string.release_text_2), + textAlign = TextAlign.Center + ) + } + }, + confirmButton = { + val uriHandler = LocalUriHandler.current + + TextButton( + onClick = { + uriHandler.openUri(component.release.url ?: Constants.GITHUB_RELEASE) + } + ) { + Image( + modifier = Modifier.size(ButtonDefaults.IconSize), + painter = painterResource(MokoRes.images.github), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.view)) + } + } + ) +} + +internal actual val DeviceIcon: ImageVector + @Composable + get() = Icons.Rounded.BrowserUpdated \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.jvm.kt new file mode 100644 index 00000000..9cb8e199 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/home/dialog/settings/component/SyncSection.jvm.kt @@ -0,0 +1,6 @@ +package dev.datlag.burningseries.ui.navigation.screen.home.dialog.settings.component + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.ui.Modifier + +actual fun LazyListScope.SyncSection() { } \ No newline at end of file diff --git a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/MediaPlayer.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/MediaPlayer.kt similarity index 78% rename from app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/MediaPlayer.kt rename to composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/MediaPlayer.kt index aa8b4d5b..3c2def42 100644 --- a/app/shared/src/desktopMain/kotlin/dev/datlag/burningseries/shared/ui/screen/video/MediaPlayer.kt +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/MediaPlayer.kt @@ -1,15 +1,18 @@ -package dev.datlag.burningseries.shared.ui.screen.video +package dev.datlag.burningseries.ui.navigation.screen.video import androidx.compose.runtime.MutableFloatState import androidx.compose.runtime.MutableLongState import androidx.compose.runtime.MutableState +import java.awt.Component interface MediaPlayer { + val component: Component val isPlaying: MutableState val time: MutableLongState val length: MutableLongState val isMuted: MutableState val volume: MutableFloatState + fun play() fun pause() fun rewind() @@ -18,4 +21,5 @@ interface MediaPlayer { fun mute() fun unmute() fun setVolume(volume: Float) + fun startPlaying() } \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoControls.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoControls.kt new file mode 100644 index 00000000..8a96502e --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoControls.kt @@ -0,0 +1,170 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.VolumeOff +import androidx.compose.material.icons.automirrored.rounded.VolumeUp +import androidx.compose.material.icons.rounded.FastForward +import androidx.compose.material.icons.rounded.FastRewind +import androidx.compose.material.icons.rounded.Fullscreen +import androidx.compose.material.icons.rounded.FullscreenExit +import androidx.compose.material.icons.rounded.Pause +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +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.WindowPlacement +import dev.datlag.burningseries.LocalWindow +import dev.datlag.burningseries.common.toDuration +import dev.datlag.tooling.async.scopeCatching + +@Composable +fun VideoControls(mediaPlayer: MediaPlayer) { + BottomAppBar( + containerColor = Color.Black, + contentColor = Color.White + ) { + val isPlaying by mediaPlayer.isPlaying + val time by remember { + derivedStateOf { mediaPlayer.time.value } + } + val length by remember { + derivedStateOf { mediaPlayer.length.value } + } + val isMuted by mediaPlayer.isMuted + val window = LocalWindow.current + var originalWindowPlacement = remember { window?.placement ?: WindowPlacement.Floating } + + IconButton( + onClick = { + mediaPlayer.rewind() + } + ) { + Icon( + imageVector = Icons.Rounded.FastRewind, + contentDescription = null + ) + } + IconButton( + onClick = { + if (isPlaying) { + mediaPlayer.pause() + } else { + mediaPlayer.play() + } + } + ) { + Icon( + imageVector = if (isPlaying) { + Icons.Rounded.Pause + } else { + Icons.Rounded.PlayArrow + }, + contentDescription = null + ) + } + IconButton( + onClick = { + mediaPlayer.forward() + } + ) { + Icon( + imageVector = Icons.Rounded.FastForward, + contentDescription = null + ) + } + Text( + text = time.toDuration(), + textAlign = TextAlign.Center + ) + Slider( + modifier = Modifier.weight(1F), + value = time.toDouble().toFloat(), + onValueChange = { + mediaPlayer.seekTo(it.toLong()) + }, + valueRange = 0F..length.toDouble().toFloat(), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = Color.White.copy(alpha = 0.2F) + ) + ) + Text( + text = length.toDuration(), + textAlign = TextAlign.Center + ) + if (window?.placement == WindowPlacement.Fullscreen) { + IconButton( + onClick = { + window.placement = originalWindowPlacement + } + ) { + Icon( + imageVector = Icons.Rounded.FullscreenExit, + contentDescription = null + ) + } + } else { + IconButton( + onClick = { + originalWindowPlacement = window?.placement ?: originalWindowPlacement + window?.placement = WindowPlacement.Fullscreen + } + ) { + Icon( + imageVector = Icons.Rounded.Fullscreen, + contentDescription = null + ) + } + } + IconButton( + onClick = { + if (isMuted) { + mediaPlayer.unmute() + } else { + mediaPlayer.mute() + } + } + ) { + Icon( + imageVector = if (isMuted) { + Icons.AutoMirrored.Rounded.VolumeOff + } else { + Icons.AutoMirrored.Rounded.VolumeUp + }, + contentDescription = null + ) + } + Slider( + value = mediaPlayer.volume.value, + onValueChange = { + mediaPlayer.unmute() + mediaPlayer.setVolume(it) + }, + valueRange = 0F..100F, + modifier = Modifier.width(100.dp) + ) + + DisposableEffect(window) { + onDispose { + scopeCatching { + window?.placement = originalWindowPlacement + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoInfo.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoInfo.kt new file mode 100644 index 00000000..006b8bdd --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoInfo.kt @@ -0,0 +1,74 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color +import dev.datlag.burningseries.common.icon +import dev.datlag.burningseries.common.isConnectedOrConnecting +import dev.datlag.kast.Kast +import dev.datlag.kast.UnselectReason +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun VideoInfo(component: VideoComponent) { + TopAppBar( + navigationIcon = { + IconButton( + onClick = { + component.back() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + }, + title = { + Text(text = component.episode.title) + }, + actions = { + val allAvailableDevices by Kast.allAvailableDevices.collectAsStateWithLifecycle() + val connectionState by Kast.connectionState.collectAsStateWithLifecycle() + + if (allAvailableDevices.isNotEmpty()) { + IconButton( + onClick = { }, + enabled = Kast.isSupported + ) { + Icon( + imageVector = connectionState.icon, + contentDescription = null + ) + } + } else if (connectionState.isConnectedOrConnecting) { + IconButton( + onClick = { + Kast.unselect(UnselectReason.disconnected) + } + ) { + Icon( + imageVector = connectionState.icon, + contentDescription = null + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Black, + scrolledContainerColor = Color.Black, + navigationIconContentColor = Color.White, + titleContentColor = Color.White, + actionIconContentColor = Color.White + ) + ) +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoPlayer.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoPlayer.kt new file mode 100644 index 00000000..4a381a43 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoPlayer.kt @@ -0,0 +1,216 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableFloatState +import androidx.compose.runtime.MutableLongState +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +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.awt.SwingPanel +import androidx.compose.ui.graphics.Color +import com.vanniktech.blurhash.BlurHash +import dev.datlag.tooling.Platform +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableSet +import uk.co.caprica.vlcj.player.base.MediaPlayer +import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter +import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent +import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent +import uk.co.caprica.vlcj.player.component.MediaPlayerComponent +import java.awt.Component +import kotlin.math.roundToInt + +@Composable +internal fun VideoPlayer( + component: VideoComponent +): dev.datlag.burningseries.ui.navigation.screen.video.MediaPlayer { + val mediaPlayerComponent = remember { + if (Platform.isMacOS) { + CallbackMediaPlayerComponent() + } else { + EmbeddedMediaPlayerComponent() + } + } + + val streamList = remember { component.streams.toImmutableList() } + + var streamIndex by remember(streamList) { mutableIntStateOf(0) } + val (url, headers) = remember(streamIndex) { + streamList[streamIndex] + } + + val isPlaying = remember { mutableStateOf(false) } + val length = remember { mutableLongStateOf(0) } + val time = remember { mutableLongStateOf(0) } + val isMuted = remember { mutableStateOf(mediaPlayerComponent.mediaPlayer()?.audio()?.isMute ?: false) } + val volumeState = remember { + val current = mediaPlayerComponent.mediaPlayer()?.audio()?.volume()?.toFloat() ?: 0F + val set = if (current <= 0F) { + if (isMuted.value) { + 0F + } else { + 1F + } + } else { + current + } + + mutableFloatStateOf(set) + } + + val eventListener = remember { object : MediaPlayerEventAdapter() { + override fun error(mediaPlayer: MediaPlayer?) { + super.error(mediaPlayer) + + if (streamList.size - 1 > streamIndex) { + streamIndex++ + } + } + + override fun lengthChanged(mediaPlayer: MediaPlayer?, newLength: Long) { + super.lengthChanged(mediaPlayer, newLength) + + component.length(newLength) + length.value = newLength + } + + override fun finished(mediaPlayer: MediaPlayer?) { + super.finished(mediaPlayer) + + component.ended() + } + + override fun timeChanged(mediaPlayer: MediaPlayer?, newTime: Long) { + super.timeChanged(mediaPlayer, newTime) + + component.progress(newTime) + time.value = newTime + } + + override fun playing(mediaPlayer: MediaPlayer?) { + super.playing(mediaPlayer) + + isPlaying.value = true + } + + override fun paused(mediaPlayer: MediaPlayer?) { + super.paused(mediaPlayer) + + isPlaying.value = false + } + + override fun opening(mediaPlayer: MediaPlayer?) { + super.opening(mediaPlayer) + + (mediaPlayer ?: mediaPlayerComponent.mediaPlayer())?.controls()?.setTime(component.startingPos) + } + + override fun muted(mediaPlayer: MediaPlayer?, muted: Boolean) { + super.muted(mediaPlayer, muted) + + isMuted.value = muted + } + + override fun volumeChanged(mediaPlayer: MediaPlayer?, volume: Float) { + super.volumeChanged(mediaPlayer, volume) + + volumeState.value = volume * 100 + } + } } + + LaunchedEffect(mediaPlayerComponent, eventListener) { + mediaPlayerComponent.mediaPlayer()?.events()?.addMediaPlayerEventListener(eventListener) + } + + SideEffect { + mediaPlayerComponent.mediaPlayer().applyHeaders(headers) + mediaPlayerComponent.mediaPlayer()?.media()?.prepare(url) + } + + DisposableEffect(mediaPlayerComponent) { + onDispose { + mediaPlayerComponent.mediaPlayer()?.release() + } + } + + return remember(mediaPlayerComponent) { object : dev.datlag.burningseries.ui.navigation.screen.video.MediaPlayer { + override val component: Component = mediaPlayerComponent + override val isPlaying: MutableState = isPlaying + override val length: MutableLongState = length + override val time: MutableLongState = time + override val isMuted: MutableState = isMuted + override val volume: MutableFloatState = volumeState + + override fun play() { + mediaPlayerComponent.mediaPlayer()?.controls()?.play() + } + + override fun pause() { + mediaPlayerComponent.mediaPlayer()?.controls()?.pause() + } + + override fun rewind() { + mediaPlayerComponent.mediaPlayer()?.controls()?.skipTime(-10000) + } + + override fun forward() { + mediaPlayerComponent.mediaPlayer()?.controls()?.skipTime(10000) + } + + override fun seekTo(millis: Long) { + mediaPlayerComponent.mediaPlayer()?.controls()?.setTime(millis) + } + + override fun mute() { + mediaPlayerComponent.mediaPlayer()?.audio()?.isMute = true + } + + override fun unmute() { + mediaPlayerComponent.mediaPlayer()?.audio()?.isMute = false + } + + override fun setVolume(volume: Float) { + mediaPlayerComponent.mediaPlayer()?.audio()?.setVolume(volume.roundToInt()) + } + + override fun startPlaying() { + mediaPlayerComponent.mediaPlayer()?.controls()?.play() + } + } } +} + +private fun MediaPlayerComponent.mediaPlayer(): MediaPlayer? { + return when (this) { + is CallbackMediaPlayerComponent -> mediaPlayer() + is EmbeddedMediaPlayerComponent -> mediaPlayer() + else -> null + } +} + +private fun MediaPlayer?.applyHeaders(headers: Map) { + if (headers.containsKey("Referer")) { + val referer = headers.getOrElse("Referer") { + headers.entries.firstNotNullOf { + if (it.key.equals("Referer", true)) { + it.value + } else { + null + } + } + } + this?.media()?.options()?.add("--http-referrer", referer) + } +} diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.jvm.kt new file mode 100644 index 00000000..2c6600fe --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/navigation/screen/video/VideoScreen.jvm.kt @@ -0,0 +1,120 @@ +package dev.datlag.burningseries.ui.navigation.screen.video + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.Download +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.SwingPanel +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import dev.datlag.burningseries.composeapp.generated.resources.Res +import dev.datlag.burningseries.composeapp.generated.resources.download +import dev.datlag.burningseries.composeapp.generated.resources.vlc_required_1 +import dev.datlag.burningseries.composeapp.generated.resources.vlc_required_2 +import dev.datlag.burningseries.other.Constants +import org.jetbrains.compose.resources.stringResource +import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +actual fun VideoScreen(component: VideoComponent) { + val foundVlc = NativeDiscovery().discover() + + if (foundVlc) { + val mediaPlayer = VideoPlayer(component) + + Scaffold( + modifier = Modifier.fillMaxSize().background(Color.Black), + topBar = { + VideoInfo(component) + }, + bottomBar = { + VideoControls(mediaPlayer) + } + ) { padding -> + Box( + modifier = Modifier.padding(padding).fillMaxSize(), + contentAlignment = Alignment.Center + ) { + SwingPanel( + background = Color.Black, + modifier = Modifier.fillMaxSize(), + factory = { mediaPlayer.component } + ) + + SideEffect { + mediaPlayer.startPlaying() + } + } + } + } else { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7F), + text = stringResource(Res.string.vlc_required_1), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7F), + text = stringResource(Res.string.vlc_required_2), + textAlign = TextAlign.Center + ) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically + ) { + val uriHandler = LocalUriHandler.current + + FilledTonalIconButton( + onClick = { + component.back() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = null + ) + } + Button( + onClick = { + uriHandler.openUri(Constants.VLC_WEBSITE) + } + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Rounded.Download, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = stringResource(Res.string.download)) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.jvm.kt new file mode 100644 index 00000000..934a073a --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/datlag/burningseries/ui/theme/Colors.jvm.kt @@ -0,0 +1,14 @@ +package dev.datlag.burningseries.ui.theme + +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.Composable + +@Composable +actual fun Colors.dynamicDark(): ColorScheme { + return getDarkScheme() +} + +@Composable +actual fun Colors.dynamicLight(): ColorScheme { + return getLightScheme() +} \ No newline at end of file diff --git a/app/desktop/resources/common/.gitkeep b/composeApp/src/jvmMain/resources/common/.gitkeep similarity index 100% rename from app/desktop/resources/common/.gitkeep rename to composeApp/src/jvmMain/resources/common/.gitkeep diff --git a/app/desktop/resources/linux-arm64/.gitkeepLinuxArm64 b/composeApp/src/jvmMain/resources/linux-arm64/.gitkeepLinuxArm64 similarity index 100% rename from app/desktop/resources/linux-arm64/.gitkeepLinuxArm64 rename to composeApp/src/jvmMain/resources/linux-arm64/.gitkeepLinuxArm64 diff --git a/app/desktop/resources/linux-x64/.gitkeepLinuxX64 b/composeApp/src/jvmMain/resources/linux-x64/.gitkeepLinuxX64 similarity index 100% rename from app/desktop/resources/linux-x64/.gitkeepLinuxX64 rename to composeApp/src/jvmMain/resources/linux-x64/.gitkeepLinuxX64 diff --git a/app/desktop/resources/macos-arm64/.gitkeepMacOSArm64 b/composeApp/src/jvmMain/resources/macos-arm64/.gitkeepMacOSArm64 similarity index 100% rename from app/desktop/resources/macos-arm64/.gitkeepMacOSArm64 rename to composeApp/src/jvmMain/resources/macos-arm64/.gitkeepMacOSArm64 diff --git a/app/desktop/resources/macos-x64/.gitkeepMacOSX64 b/composeApp/src/jvmMain/resources/macos-x64/.gitkeepMacOSX64 similarity index 100% rename from app/desktop/resources/macos-x64/.gitkeepMacOSX64 rename to composeApp/src/jvmMain/resources/macos-x64/.gitkeepMacOSX64 diff --git a/app/desktop/resources/windows/.gitkeepWindows b/composeApp/src/jvmMain/resources/windows/.gitkeepWindows similarity index 100% rename from app/desktop/resources/windows/.gitkeepWindows rename to composeApp/src/jvmMain/resources/windows/.gitkeepWindows diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 26025db1..a1fcab12 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -4,18 +4,9 @@ plugins { alias(libs.plugins.sqldelight) } -val artifact = VersionCatalog.artifactName("database") - -group = artifact - kotlin { jvm() androidTarget() - iosX64() - iosArm64() - iosSimulatorArm64() - - jvmToolchain(CompileOptions.jvmTargetVersion) applyDefaultHierarchyTemplate() @@ -24,7 +15,9 @@ kotlin { dependencies { api(libs.sqldelight.coroutines) api(project(":model")) - implementation(libs.sqldelight.adapter.bool) + implementation(libs.sqldelight.adapter) + implementation(libs.tooling) + implementation(libs.datetime) } } @@ -43,35 +36,24 @@ kotlin { } } -android { - compileSdk = Configuration.compileSdk - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - - namespace = artifact - - defaultConfig { - minSdk = Configuration.minSdk - } - - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } - packaging { - resources.merges.add("META-INF/LICENSE") - resources.merges.add("META-INF/DEPENDENCIES") - resources.pickFirsts.add("**") - resources.pickFirsts.add("**/*") - resources.pickFirsts.add("*") - resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin") - } -} - sqldelight { databases { create("BurningSeries") { - packageName.set(artifact) + packageName.set("dev.datlag.burningseries.database") srcDirs("src/commonMain/bs") } } +} + +android { + compileSdk = 34 + namespace = "dev.datlag.burningseries.database" + + defaultConfig { + minSdk = 23 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } } \ No newline at end of file diff --git a/database/src/androidMain/kotlin/dev/datlag/burningseries/database/DriverFactory.android.kt b/database/src/androidMain/kotlin/dev/datlag/burningseries/database/DriverFactory.android.kt index c0c9bf89..1475fb3f 100644 --- a/database/src/androidMain/kotlin/dev/datlag/burningseries/database/DriverFactory.android.kt +++ b/database/src/androidMain/kotlin/dev/datlag/burningseries/database/DriverFactory.android.kt @@ -1,27 +1,43 @@ package dev.datlag.burningseries.database import android.content.Context +import android.content.ContextWrapper import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteQuery import androidx.sqlite.db.SupportSQLiteQueryBuilder import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import dev.datlag.burningseries.model.common.scopeCatching +import dev.datlag.tooling.existsSafely +import dev.datlag.tooling.isDirectorySafely +import dev.datlag.tooling.mkdirsSafely +import dev.datlag.tooling.parentSafely +import java.io.File actual class DriverFactory( - private val context: Context + context: Context ) { - actual fun createBurningSeriesDriver(): SqlDriver { - val driver = AndroidSqliteDriver(BurningSeries.Schema, context, "bs.db") - scopeCatching { - BurningSeries.Schema.migrate( - driver = driver, - oldVersion = 0, - newVersion = BurningSeries.Schema.version - ) + private val databaseContext = object : ContextWrapper(context) { + override fun getDatabasePath(name: String?): File { + val superFile = super.getDatabasePath(name) + val (defaultFolder, defaultName) = if (superFile.isDirectorySafely()) { + (superFile ?: context.filesDir) to (name ?: "database.db") + } else { + (superFile?.parentSafely() ?: context.filesDir) to (name?.ifBlank { null } ?: superFile.name) + } + + val versionedFolder = File(defaultFolder, "v6") + if (!versionedFolder.existsSafely()) { + versionedFolder.mkdirsSafely() + } + return File(versionedFolder, defaultName) } + } + + actual fun createBurningSeriesDriver(): SqlDriver { + val driver = AndroidSqliteDriver(BurningSeries.Schema, databaseContext, "bs.db") + return driver } @@ -30,7 +46,7 @@ actual class DriverFactory( val callback = AndroidSqliteDriver.Callback(BurningSeries.Schema) return factory.create( - SupportSQLiteOpenHelper.Configuration.builder(context) + SupportSQLiteOpenHelper.Configuration.builder(databaseContext) .callback(callback) .name("bs.db") .noBackupDirectory(false) diff --git a/database/src/commonMain/bs/dev/datlag/burningseries/database/BurningSeries.sq b/database/src/commonMain/bs/dev/datlag/burningseries/database/BurningSeries.sq index 700f88f4..251b0229 100644 --- a/database/src/commonMain/bs/dev/datlag/burningseries/database/BurningSeries.sq +++ b/database/src/commonMain/bs/dev/datlag/burningseries/database/BurningSeries.sq @@ -1,96 +1,126 @@ import kotlin.Boolean; +import kotlin.Int; +import kotlin.collections.List; +import kotlinx.datetime.Instant; CREATE TABLE IF NOT EXISTS Series( hrefPrimary TEXT NOT NULL PRIMARY KEY, href TEXT NOT NULL, - title TEXT NOT NULL, - coverHref TEXT, - favoriteSince INTEGER NOT NULL, - anime INTEGER AS Boolean NOT NULL DEFAULT 0 + season INTEGER AS Int, + seasons TEXT AS List NOT NULL, + coverHref TEXT DEFAULT NULL, + fullTitle TEXT NOT NULL, + mainTitle TEXT DEFAULT NULL, + subTitle TEXT DEFAULT NULL, + isAnime INTEGER AS Boolean NOT NULL DEFAULT 0, + favoriteSince INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS Episode( href TEXT NOT NULL PRIMARY KEY, - number TEXT NOT NULL, + number INTEGER AS Int NOT NULL, title TEXT NOT NULL, - length INTEGER NOT NULL, - progress INTEGER NOT NULL, + length INTEGER NOT NULL DEFAULT 0, + progress INTEGER NOT NULL DEFAULT 0, + watching INTEGER AS Boolean NOT NULL DEFAULT 0, + finished INTEGER AS Boolean NOT NULL DEFAULT 0, + blurHash TEXT DEFAULT NULL, + updatedAt INTEGER AS Instant NOT NULL DEFAULT 0, seriesHref TEXT NOT NULL, FOREIGN KEY (seriesHref) REFERENCES Series(hrefPrimary) ON DELETE NO ACTION ); -CREATE TABLE IF NOT EXISTS Hoster( - href TEXT NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - episodeHref TEXT NOT NULL, - FOREIGN KEY (episodeHref) REFERENCES Episode(href) ON DELETE CASCADE -); +insertSeriesOrIgnore: +INSERT OR IGNORE INTO Series( + hrefPrimary, + href, + season, + seasons, + coverHref, + fullTitle, + mainTitle, + subTitle, + isAnime, + favoriteSince +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +favoriteSeriesSorted: +SELECT s.* FROM Series s JOIN ( + SELECT seriesHref, MAX(updatedAt) AS latestUpdate FROM Episode GROUP BY seriesHref +) e ON s.hrefPrimary = e.seriesHref WHERE s.favoriteSince > 0 ORDER BY +CASE WHEN e.latestUpdate > s.favoriteSince THEN e.latestUpdate +ELSE s.favoriteSince END DESC; + +upsertSeriesFavoriteSince { + UPDATE Series SET favoriteSince = :since WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; -CREATE TABLE IF NOT EXISTS SearchItem( - href TEXT NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - genre TEXT NOT NULL -); + INSERT OR IGNORE INTO Series(hrefPrimary, href, season, seasons, coverHref, fullTitle, mainTitle, subTitle, isAnime, favoriteSince) VALUES (:hrefPrimary, :href, :season, :seasons, :coverHref, :fullTitle, :mainTitle, :subTitle, :isAnime, :since); +} -insertSeries: -INSERT OR REPLACE INTO Series(hrefPrimary, href, title, coverHref, favoriteSince, anime) VALUES (?, ?, ?, ?, ?, ?); +seriesIsFavoriteByHref: +SELECT favoriteSince > 0 FROM Series WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; -insertSeriesOrIgnore: -INSERT OR IGNORE INTO Series(hrefPrimary, href, title, coverHref, favoriteSince, anime) VALUES (?, ?, ?, ?, ?, ?); +updateSeriesHref: +UPDATE Series SET href = :value WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; -favoriteSeries: -SELECT * FROM Series WHERE favoriteSince > 0 ORDER BY favoriteSince DESC; +updateSeriesSeason: +UPDATE Series SET season = :value WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; -seriesByHref: -SELECT * FROM Series WHERE hrefPrimary = :href OR hrefPrimary LIKE :href; +selectSeriesFullHref: +SELECT href FROM Series WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; -seriesUpdateHrefByCommonHref: -UPDATE Series SET href = :href WHERE hrefPrimary = :commonHref OR hrefPrimary LIKE :commonHref; +insertEpisodeOrIgnore: +INSERT OR IGNORE INTO Episode( + href, + number, + title, + length, + progress, + watching, + finished, + blurHash, + updatedAt, + seriesHref +) VALUES (?, ?, ?, 0, 0, 0, 0, NULL, ?, ?); -updateSeriesFavoriteSince { - UPDATE Series SET favoriteSince = :since WHERE hrefPrimary = :hrefPrimary OR hrefPrimary LIKE :hrefPrimary; +updateEpisodeLength: +UPDATE Episode SET length = :value WHERE href = :href OR href LIKE :href; - INSERT OR IGNORE INTO Series (hrefPrimary, href, title, coverHref, favoriteSince, anime) VALUES (:hrefPrimary, :href, :title, :coverHref, :since, :anime); -} +updateEpisodeProgress: +UPDATE Episode SET progress = :value, watching = :value > 0, finished = (CAST(:value AS REAL) / CAST(length AS REAL) * 100.0) >= 85.0, updatedAt = :updated WHERE href = :href OR href LIKE :href; -insertEpisode: -INSERT OR IGNORE INTO Episode(href, number, title, length, progress, seriesHref) VALUES (?, ?, ?, ?, ?, ?); +updateEpisodeBlurHash: +UPDATE Episode SET blurHash = :value WHERE href = :href OR href LIKE :href; -updateEpisodeLength { - UPDATE Episode SET length = :length WHERE href = :href OR href LIKE :href; +selectEpisodeProgress: +SELECT progress FROM Episode WHERE href = :href OR href LIKE :href; - INSERT OR IGNORE INTO Episode (href, number, title, length, progress, seriesHref) VALUES (:href, :number, :title, :length, :progress, :seriesHref); -} +selectEpisodeLength: +SELECT progress FROM Episode WHERE href = :href OR href LIKE :href; -updateEpisodeProgress { - UPDATE Episode SET progress = :progress WHERE href = :href OR href LIKE :href; - - INSERT OR IGNORE INTO Episode (href, number, title, length, progress, seriesHref) VALUES (:href, :number, :title, :length, :progress, :seriesHref); -} +episodeMarkWatched: +UPDATE Episode SET finished = 1, updatedAt = :updated WHERE href = :href OR href LIKE :href; -selectEpisodeByHref: -SELECT * FROM Episode WHERE href = :href OR href LIKE :href; +episodeMarkUnwatched: +UPDATE Episode SET progress = CASE WHEN (CAST(progress AS REAL) / CAST(length AS REAL) * 100.0) >= 85.0 THEN 0 ELSE progress END, finished = 0, updatedAt = :updated WHERE href = :href OR href LIKE :href; -selectEpisodesBySeriesHref: +selectEpisodeBySeriesHref: SELECT * FROM Episode WHERE seriesHref = :href OR seriesHref LIKE :href; -insertHoster: -INSERT OR IGNORE INTO Hoster (href, title, episodeHref) VALUES (?, ?, ?); - -selectHosterByEpisodeHref: -SELECT * FROM Hoster WHERE episodeHref = :href OR episodeHref LIKE :href; +selectEpisodeWatchingByHref: +SELECT watching FROM Episode WHERE href = :href OR href LIKE :href; -insertSearchItem: -INSERT OR IGNORE INTO SearchItem (href, title, genre) VALUES ?; +selectEpisodeFinishedByHref: +SELECT finished FROM Episode WHERE href = :href OR href LIKE :href; -selectAllSearchItems: -SELECT * FROM SearchItem; +selectEpisodeBlurHashByHref: +SELECT blurHash FROM Episode WHERE href = :href OR href LIKE :href; -setSeriesAnime: -UPDATE Series SET anime = 1 WHERE hrefPrimary = :href; +selectEpisodeNumberByHref: +SELECT number FROM Episode WHERE href = :href OR href LIKE :href; -unsetSeriesAnime: -UPDATE Series SET anime = 0 WHERE hrefPrimary = :href; +selectEpisodeByHref: +SELECT * FROM Episode WHERE href = :href OR href LIKE :href; -isSeriesAnime: -SELECT anime FROM Series WHERE hrefPrimary = :href; \ No newline at end of file +countWatchedEpisode: +SELECT COUNT(DISTINCT href) FROM Episode WHERE watching = 1 OR finished = 1; \ No newline at end of file diff --git a/database/src/commonMain/bs/migrations/1.sqm b/database/src/commonMain/bs/migrations/1.sqm deleted file mode 100644 index 50dfeca6..00000000 --- a/database/src/commonMain/bs/migrations/1.sqm +++ /dev/null @@ -1,3 +0,0 @@ -import kotlin.Boolean; - -ALTER TABLE Series ADD COLUMN anime INTEGER AS Boolean NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/CombinedEpisode.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/CombinedEpisode.kt new file mode 100644 index 00000000..da86ff39 --- /dev/null +++ b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/CombinedEpisode.kt @@ -0,0 +1,63 @@ +package dev.datlag.burningseries.database + +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.model.SeriesData + +data class CombinedEpisode @JvmOverloads constructor( + val default: Series.Episode, + val database: Episode? = null +) : SeriesData() { + override val href + get() = default.href + + override val title: String + get() = default.title + + override val coverHref: String? + get() = default.coverHref + + override val mainTitle: String + get() = default.mainTitle + + override val subTitle: String? + get() = default.subTitle + + override val season: Int? + get() = default.season + + override val language: String? + get() = default.language + + val progress: Long + get() = database?.progress ?: 0 + + val length: Long + get() = database?.length ?: 0 + + val isFinished: Boolean + get() = database?.finished ?: false + + val isWatching: Boolean + get() = database?.watching ?: false + + val number: Int + get() = default.convertedNumber ?: database?.number ?: -1 + + val blurHash: String? + get() = database?.blurHash?.ifBlank { null } + + val hasHoster: Boolean + get() = default.hoster.isNotEmpty() + + fun isSame(other: Series.Episode?): Boolean { + return default == other + } + + fun ifHasHoster(): CombinedEpisode? { + return if (default.hoster.isEmpty()) { + null + } else { + this + } + } +} diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/ExtendedSeries.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/ExtendedSeries.kt new file mode 100644 index 00000000..7c80b028 --- /dev/null +++ b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/ExtendedSeries.kt @@ -0,0 +1,16 @@ +package dev.datlag.burningseries.database + +import dev.datlag.burningseries.model.SeriesData + +data class ExtendedSeries( + val database: Series +) : SeriesData() { + override val href: String + get() = database.href + + override val title: String + get() = database.fullTitle + + override val coverHref: String? + get() = database.coverHref +} diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/InstantAdapter.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/InstantAdapter.kt new file mode 100644 index 00000000..e6143f45 --- /dev/null +++ b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/InstantAdapter.kt @@ -0,0 +1,19 @@ +package dev.datlag.burningseries.database + +import app.cash.sqldelight.ColumnAdapter +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant + +object InstantAdapter : ColumnAdapter { + override fun decode(databaseValue: Long): Instant { + return if (databaseValue <= 0L) { + Clock.System.now() + } else { + Instant.fromEpochSeconds(databaseValue) + } + } + + override fun encode(value: Instant): Long { + return value.epochSeconds + } +} \ No newline at end of file diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/IntAdapter.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/IntAdapter.kt new file mode 100644 index 00000000..26123312 --- /dev/null +++ b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/IntAdapter.kt @@ -0,0 +1,13 @@ +package dev.datlag.burningseries.database + +import app.cash.sqldelight.ColumnAdapter + +object IntAdapter : ColumnAdapter { + override fun decode(databaseValue: Long): Int { + return databaseValue.toInt() + } + + override fun encode(value: Int): Long { + return value.toLong() + } +} \ No newline at end of file diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendDatabase.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendDatabase.kt new file mode 100644 index 00000000..abce3f40 --- /dev/null +++ b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendDatabase.kt @@ -0,0 +1,294 @@ +package dev.datlag.burningseries.database.common + +import app.cash.sqldelight.Query +import app.cash.sqldelight.coroutines.asFlow +import app.cash.sqldelight.coroutines.mapToList +import app.cash.sqldelight.coroutines.mapToOneOrDefault +import app.cash.sqldelight.coroutines.mapToOneOrNull +import dev.datlag.burningseries.database.BurningSeries +import dev.datlag.burningseries.database.CombinedEpisode +import dev.datlag.burningseries.database.Episode +import dev.datlag.burningseries.database.ExtendedSeries +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.model.SeriesData +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.datetime.Clock +import kotlin.coroutines.CoroutineContext + +fun BurningSeries.isFavorite(seriesData: SeriesData, context: CoroutineContext): Flow { + return this.burningSeriesQueries.seriesIsFavoriteByHref(seriesData.source).asFlow().mapToOneOrDefault(false, context) +} + +fun BurningSeries.isFavoriteOneShot(seriesData: SeriesData): Boolean { + return this.burningSeriesQueries.seriesIsFavoriteByHref(seriesData.source).executeAsOneOrNull() ?: false +} + +fun BurningSeries.setSeriesFavorite(series: Series) { + val fallbackSeason = if (series.seasons.size == 1) { + series.seasons.firstOrNull()?.value + } else { + null + } + + this.burningSeriesQueries.upsertSeriesFavoriteSince( + since = Clock.System.now().epochSeconds, + hrefPrimary = series.source, + href = series.href, + season = series.season ?: fallbackSeason, + seasons = series.seasons.map { it.value }, + coverHref = series.coverHref, + fullTitle = series.title, + mainTitle = series.mainTitle, + subTitle = series.subTitle, + isAnime = series.isAnime + ) + + this.burningSeriesQueries.transaction { + series.episodes.forEach { episode -> + insertEpisodeOrIgnore( + episode = episode, + series = series + ) + } + } +} + +fun BurningSeries.unsetSeriesFavorite(series: Series) { + val fallbackSeason = if (series.seasons.size == 1) { + series.seasons.firstOrNull()?.value + } else { + null + } + + this.burningSeriesQueries.upsertSeriesFavoriteSince( + since = 0L, + hrefPrimary = series.source, + href = series.href, + season = series.season ?: fallbackSeason, + seasons = series.seasons.map { it.value }, + coverHref = series.coverHref, + fullTitle = series.title, + mainTitle = series.mainTitle, + subTitle = series.subTitle, + isAnime = series.isAnime + ) +} + +fun BurningSeries.favoritesSeries(context: CoroutineContext): Flow> { + return this.burningSeriesQueries.favoriteSeriesSorted().asFlow().mapToList(context).map { collection -> + collection.map(::ExtendedSeries).toImmutableSet() + } +} + +fun BurningSeries.favoritesSeriesOneShot(): ImmutableCollection { + return this.burningSeriesQueries.favoriteSeriesSorted().executeAsList().map(::ExtendedSeries).toImmutableSet() +} + +fun BurningSeries.insertSeriesOrIgnore(series: Series) { + val fallbackSeason = if (series.seasons.size == 1) { + series.seasons.firstOrNull()?.value + } else { + null + } + + this.burningSeriesQueries.insertSeriesOrIgnore( + hrefPrimary = series.source, + href = series.href, + season = series.season ?: fallbackSeason, + seasons = series.seasons.map { it.value }, + coverHref = series.coverHref, + fullTitle = series.title, + mainTitle = series.mainTitle, + subTitle = series.subTitle, + isAnime = series.isAnime, + favoriteSince = 0L + ) +} + +fun BurningSeries.updateSeriesHref(seriesData: SeriesData, value: String = seriesData.toHref()) { + this.burningSeriesQueries.updateSeriesHref( + value = value, + hrefPrimary = seriesData.source + ) +} + +fun BurningSeries.updateSeriesSeason(seriesData: SeriesData, value: Int? = seriesData.season) { + this.burningSeriesQueries.updateSeriesSeason( + value = value, + hrefPrimary = seriesData.source + ) +} + +fun BurningSeries.updateSeriesData(seriesData: SeriesData) { + this.updateSeriesHref(seriesData) + this.updateSeriesSeason(seriesData) +} + +fun BurningSeries.updateLength(value: Long, episode: Series.Episode) { + this.burningSeriesQueries.updateEpisodeLength( + value = value, + href = episode.href + ) +} + +fun BurningSeries.updateProgress(value: Long, episode: Series.Episode) { + this.burningSeriesQueries.updateEpisodeProgress( + value = value, + href = episode.href, + updated = Clock.System.now() + ) +} + +fun BurningSeries.insertEpisodeOrIgnore( + episode: Series.Episode, + series: Series +) { + this.insertSeriesOrIgnore(series) + + this.insertEpisodeOrIgnore( + episode = episode, + fallbackNumber = series.episodes.indexOf(episode) + 1, + seriesHref = series.source + ) +} + +fun BurningSeries.insertEpisodeOrIgnore( + episode: Series.Episode, + fallbackNumber: Int, + seriesHref: String, +) { + this.burningSeriesQueries.insertEpisodeOrIgnore( + href = episode.href, + number = episode.convertedNumber ?: fallbackNumber, + title = episode.fullTitle, + seriesHref = seriesHref, + updatedAt = Clock.System.now() + ) +} + +fun BurningSeries.episodeProgress(episode: Series.Episode, context: CoroutineContext): Flow { + return this.burningSeriesQueries.selectEpisodeProgress(episode.href).asFlow().mapToOneOrDefault(0L, context) +} + +fun BurningSeries.episodeProgressOneShot(episode: Series.Episode): Long { + return this.burningSeriesQueries.selectEpisodeProgress(episode.href).executeAsOneOrNull() ?: 0L +} + +fun BurningSeries.episodeLengthOneShot(episode: Series.Episode): Long { + return this.burningSeriesQueries.selectEpisodeLength(episode.href).executeAsOneOrNull() ?: 0L +} + +fun BurningSeries.updateEpisodeBlurHash(episode: Series.Episode, hash: String?) { + this.burningSeriesQueries.updateEpisodeBlurHash( + value = hash, + href = episode.href + ) +} + +fun BurningSeries.setEpisodeWatched(episode: Series.Episode) { + this.burningSeriesQueries.episodeMarkWatched( + href = episode.href, + updated = Clock.System.now() + ) +} + +fun BurningSeries.setEpisodeUnwatched(episode: Series.Episode) { + this.burningSeriesQueries.episodeMarkUnwatched( + href = episode.href, + updated = Clock.System.now() + ) +} + +fun BurningSeries.episodeForSeries(series: SeriesData): ImmutableCollection { + return this.burningSeriesQueries.selectEpisodeBySeriesHref(series.source).executeAsList().toImmutableSet() +} + +fun BurningSeries.combinedEpisodesForSeries(collection: Collection, series: SeriesData): ImmutableCollection { + val fromDatabase = episodeForSeries(series) + + return if (fromDatabase.isEmpty()) { + collection.map(::CombinedEpisode).toImmutableSet() + } else { + collection.map { episode -> + CombinedEpisode( + default = episode, + database = fromDatabase.firstOrNull { it.href == episode.href } + ) + }.toImmutableSet() + } +} + +fun BurningSeries.episodeWatching(episode: Series.Episode, context: CoroutineContext): Flow { + return this.burningSeriesQueries.selectEpisodeWatchingByHref(episode.href).asFlow().mapToOneOrDefault(false, context) +} + +fun BurningSeries.episodeWatchingOneShot(episode: Series.Episode): Boolean { + return this.burningSeriesQueries.selectEpisodeWatchingByHref(episode.href).executeAsOneOrNull() ?: false +} + +fun BurningSeries.episodeFinished(episode: Series.Episode, context: CoroutineContext): Flow { + return this.burningSeriesQueries.selectEpisodeFinishedByHref(episode.href).asFlow().mapToOneOrDefault(false, context) +} + +fun BurningSeries.episodeFinishedOneShot(episode: Series.Episode): Boolean { + return this.burningSeriesQueries.selectEpisodeFinishedByHref(episode.href).executeAsOneOrNull() ?: false +} + +fun BurningSeries.episodeBlurHash(episode: Series.Episode, context: CoroutineContext): Flow { + return this.burningSeriesQueries.selectEpisodeBlurHashByHref(episode.href).asFlow().mapToOneOrNull(context).map { it?.blurHash } +} + +fun BurningSeries.episodeBlurHashOneShot(episode: Series.Episode): String? { + return this.burningSeriesQueries.selectEpisodeBlurHashByHref(episode.href).executeAsOneOrNull()?.blurHash +} + +fun BurningSeries.episodeNumberOneShot(episode: Series.Episode): Int? { + return this.burningSeriesQueries.selectEpisodeNumberByHref(episode.href).executeAsOneOrNull() +} + +fun BurningSeries.episodeRefreshingData(episode: Series.Episode, context: CoroutineContext): Flow { + val databaseEpisode = this@episodeRefreshingData.burningSeriesQueries.selectEpisodeByHref( + episode.href + ).executeAsOneOrNull() + + return flow { + emit( + CombinedEpisode( + default = episode, + database = databaseEpisode + ) + ) + emitAll( + combine( + episodeFinished(episode, context), + episodeWatching(episode, context), + episodeProgress(episode, context), + ) { finished, watching, progress -> + CombinedEpisode( + default = episode, + database = databaseEpisode?.copy( + watching = watching, + finished = finished, + progress = progress + ) ?: this@episodeRefreshingData.burningSeriesQueries.selectEpisodeByHref( + episode.href + ).executeAsOneOrNull() + ) + } + ) + } +} + +fun BurningSeries.seriesFullHref(data: SeriesData): String? { + return this.burningSeriesQueries.selectSeriesFullHref(data.source).executeAsOneOrNull()?.ifBlank { null } +} + +fun BurningSeries.countWatchedEpisodes(): Int { + return this.burningSeriesQueries.countWatchedEpisode().executeAsOneOrNull()?.toInt() ?: 0 +} diff --git a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendModels.kt b/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendModels.kt deleted file mode 100644 index 5ba62466..00000000 --- a/database/src/commonMain/kotlin/dev/datlag/burningseries/database/common/ExtendModels.kt +++ /dev/null @@ -1,108 +0,0 @@ -package dev.datlag.burningseries.database.common - -import dev.datlag.burningseries.database.Episode -import dev.datlag.burningseries.database.Hoster -import dev.datlag.burningseries.database.SearchItem -import dev.datlag.burningseries.database.Series -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.algorithm.JaroWinkler -import dev.datlag.burningseries.model.common.getDigitsOrNull -import dev.datlag.burningseries.model.Series as ModelSeries -import dev.datlag.burningseries.model.Series.Episode as ModelEpisode -import dev.datlag.burningseries.model.Series.Episode.Hoster as ModelHoster - -fun Series.toModelSeries(episodes: List, hosters: List): ModelSeries { - return ModelSeries( - title = this.title, - description = "", - coverHref = this.coverHref, - href = this.href, - seasonTitle = "", - selectedLanguage = null, - seasons = emptyList(), - languages = emptyList(), - episodes = episodes.map { it.toModelEpisode(hosters) } - ) -} - -fun Episode.toModelEpisode(hosters: List): ModelEpisode { - return ModelEpisode( - number = this.number, - title = this.title, - href = this.href, - hosters = hosters.map { it.toModelHoster() } - ) -} - -fun Hoster.toModelHoster(): ModelHoster { - return ModelHoster( - title = this.title, - href = this.href - ) -} - -fun Genre.toSearchItems(): List { - return this.items.map { - SearchItem( - href = BSUtil.normalizeHref(it.href).trim(), - title = it.title.trim(), - genre = this.title.trim() - ) - } -} - -fun List.toGenres(): List { - return this.groupBy { it.genre.trim() }.map { (k, v) -> - Genre( - title = k.trim(), - items = v.map { - Genre.Item( - title = it.title.trim(), - href = it.href.trim() - ) - } - ) - } -} - -val Series.allTitles - get() = allTitlesCache.getOrPut(this) { - title.split('|').filterNot { it.isBlank() }.map { it.trim() }.distinct() - } - -val Series.bestTitle - get() = bestTitleCache.getOrPut(this) { - when { - allTitles.size <= 1 -> allTitles.firstOrNull() ?: title - else -> { - val newTitles = mutableListOf() - allTitles.forEach { str -> - val strFlatten = str.replace("\\s".toRegex(RegexOption.MULTILINE), String()).trim() - - if (newTitles.none { - JaroWinkler.distance(str, it) > 0.95 || run { - val itFlatten = it.replace("\\s".toRegex(RegexOption.MULTILINE), String()) - - JaroWinkler.distance(strFlatten, itFlatten) > 0.95F - } - }) { - newTitles.add(str) - } - } - newTitles.toSet().joinToString(separator = " | ") - } - } - } - -val Series.mainTitle - get() = bestTitle.substringBefore('|').trim() - -val Series.subTitle - get() = bestTitle.substringAfter('|', missingDelimiterValue = "").trim().ifBlank { null } - -private val allTitlesCache = mutableMapOf>() -private val bestTitleCache = mutableMapOf() - -val Episode.convertedNumber: Int? - get() = this.number.toIntOrNull() ?: this.number.getDigitsOrNull()?.toIntOrNull() \ No newline at end of file diff --git a/database/src/iosMain/kotlin/dev/datlag/burningseries/database/DriverFactory.ios.kt b/database/src/iosMain/kotlin/dev/datlag/burningseries/database/DriverFactory.ios.kt deleted file mode 100644 index 10dda8f8..00000000 --- a/database/src/iosMain/kotlin/dev/datlag/burningseries/database/DriverFactory.ios.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.datlag.burningseries.database - -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.native.NativeSqliteDriver - -actual class DriverFactory { - actual fun createBurningSeriesDriver(): SqlDriver { - return NativeSqliteDriver(BurningSeries.Schema, "bs.db") - } -} \ No newline at end of file diff --git a/database/src/jvmMain/kotlin/dev/datlag/burningseries/database/DriverFactory.jvm.kt b/database/src/jvmMain/kotlin/dev/datlag/burningseries/database/DriverFactory.jvm.kt index 64ad3d25..e7d4079e 100644 --- a/database/src/jvmMain/kotlin/dev/datlag/burningseries/database/DriverFactory.jvm.kt +++ b/database/src/jvmMain/kotlin/dev/datlag/burningseries/database/DriverFactory.jvm.kt @@ -2,21 +2,15 @@ package dev.datlag.burningseries.database import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver -import dev.datlag.burningseries.model.common.scopeCatching import java.io.File -actual class DriverFactory(private val file: File) { +actual class DriverFactory( + private val file: File +) { actual fun createBurningSeriesDriver(): SqlDriver { val driver = JdbcSqliteDriver("jdbc:sqlite:${file.canonicalPath}") BurningSeries.Schema.create(driver) - scopeCatching { - BurningSeries.Schema.migrate( - driver = driver, - oldVersion = 0, - newVersion = BurningSeries.Schema.version - ) - } return driver } } \ No newline at end of file diff --git a/extension/background/build.gradle.kts b/extension/background/build.gradle.kts deleted file mode 100644 index 1e7431c8..00000000 --- a/extension/background/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.serialization) - alias(libs.plugins.ktorfit) - alias(libs.plugins.sekret) -} - -sekret { - packageName = "dev.datlag.burningseries" - generateJsSourceSet = true - propertiesFile = rootDir.canonicalPath -} - -kotlin { - js(IR) { - browser { - binaries.executable() - webpackTask { - mainOutputFileName = "background.js" - sourceMaps = false - } - distribution { - outputDirectory.set(parentBuildDir("distributions")) - } - } - } - - sourceSets { - jsMain.get().dependencies { - implementation(parent?.project("base") ?: rootProject.project("extension:base")) - implementation(rootProject.project("model")) - implementation(rootProject.project("network")) - - implementation(libs.ktor) - implementation(libs.ktor.js) - implementation(libs.ktor.content.negotiation) - implementation(libs.ktor.serialization.json) - } - } -} - -tasks.build { - dependsOn(tasks.generateSekret) -} \ No newline at end of file diff --git a/extension/background/sekret/build.gradle.kts b/extension/background/sekret/build.gradle.kts deleted file mode 100644 index c57e7213..00000000 --- a/extension/background/sekret/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - alias(libs.plugins.multiplatform) -} -kotlin { - js(IR) { - binaries.executable() - browser() - nodejs() - } - - applyDefaultHierarchyTemplate() - - sourceSets { - val commonMain by getting { - dependencies { - api(libs.sekret) - } - } - } -} diff --git a/extension/background/src/jsMain/kotlin/background.kt b/extension/background/src/jsMain/kotlin/background.kt deleted file mode 100644 index 2d7ef5d8..00000000 --- a/extension/background/src/jsMain/kotlin/background.kt +++ /dev/null @@ -1,118 +0,0 @@ -import common.isNullOrEmpty -import de.jensklingenberg.ktorfit.ktorfitBuilder -import dev.datlag.burningseries.Sekret -import dev.datlag.burningseries.model.ExtensionMessage -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.scopeCatching -import dev.datlag.burningseries.model.state.SaveAction -import dev.datlag.burningseries.model.state.SaveState -import dev.datlag.burningseries.network.Firestore -import dev.datlag.burningseries.network.JsonBase -import dev.datlag.burningseries.network.realm.RealmLoader -import dev.datlag.burningseries.network.state.SaveStateMachine -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseOptions -import dev.gitlive.firebase.firestore.firestore -import dev.gitlive.firebase.initialize -import io.ktor.client.* -import io.ktor.client.engine.js.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.serialization.kotlinx.json.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.promise -import kotlinx.serialization.json.Json - -fun main() { - val defaultJson = Json { - ignoreUnknownKeys = true - isLenient = true - } - val client = HttpClient(Js) { - developmentMode = false - followRedirects = true - - install(ContentNegotiation) { - json(defaultJson) - } - } - val packageName = "dev.datlag.burningseries" - val ktorfit = ktorfitBuilder { - httpClient(client) - } - val jsonBaseKtor = ktorfit.build { - baseUrl("https://jsonbase.com/") - } - val firebaseKtor = ktorfit.build { - baseUrl("https://firestore.googleapis.com/v1/projects/${Sekret.firebaseProject(packageName)}/") - } - val app = Firebase.initialize( - context = null, - options = FirebaseOptions( - applicationId = Sekret.firebaseApplication(packageName), - apiKey = Sekret.firebaseApiKey(packageName), - projectId = Sekret.firebaseProject(packageName) - ) - ) - val store = Firebase.firestore(app) - - val jsonBase = jsonBaseKtor.create() - val firebase = firebaseKtor.create() - - val activator = SaveStateMachine( - client = client, - jsonBase = jsonBase, - realmLoader = RealmLoader, - firestore = store, - firestoreApi = firebase - ) - val activatorState = activator.state.flowOn(Dispatchers.Default).shareIn(GlobalScope, SharingStarted.Eagerly) - - val memorySaveState = mutableMapOf() - - browser.runtime.onMessage.addListener { - val msg = if (it.message.isNullOrEmpty()) { - it - } else { - it.message - }?.toString() - - if (msg.isNullOrEmpty()) { - return@addListener false - } - - val message: ExtensionMessage? = scopeCatching { - defaultJson.decodeFromString(msg!!) - }.getOrNull() ?: scopeCatching { - it.unsafeCast() - }.getOrNull() - - if (message.isNullOrEmpty()) { - return@addListener false - } - - return@addListener GlobalScope.promise { - if (message!!.set && !message.url.isNullOrBlank()) { - activator.dispatch(SaveAction.Save( - HosterScraping( - href = message.href, - url = message.url!! - ), - loadStream = false - )) - val result = activatorState.first { state -> - state is SaveState.Success || state is SaveState.Error - } - return@promise (result is SaveState.Success).also { saved -> - memorySaveState[message.href] = saved - } - } else { - return@promise memorySaveState[message.href] ?: false - } - } - } -} diff --git a/extension/base/build.gradle.kts b/extension/base/build.gradle.kts deleted file mode 100644 index 6bd11bd5..00000000 --- a/extension/base/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - alias(libs.plugins.multiplatform) -} - -kotlin { - js(IR) { - browser { - binaries.executable() - useCommonJs() - webpackTask { - mainOutputFileName.set("base.js") - sourceMaps = false - } - distribution { - outputDirectory.set(parentBuildDir("distributions")) - } - } - } - - sourceSets { - dependencies { - - } - } -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/events/Constructor.kt b/extension/base/src/jsMain/kotlin/browser/events/Constructor.kt deleted file mode 100644 index 99266ae7..00000000 --- a/extension/base/src/jsMain/kotlin/browser/events/Constructor.kt +++ /dev/null @@ -1,9 +0,0 @@ -@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") - -package browser.events - -public inline fun Rule(block: Rule.() -> Unit): Rule = (js("{}") as Rule).apply(block) - -public inline fun UrlFilter(block: UrlFilter.() -> Unit): UrlFilter = (js("{}") as - UrlFilter).apply(block) \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/events/Event.kt b/extension/base/src/jsMain/kotlin/browser/events/Event.kt deleted file mode 100644 index 7f00c46d..00000000 --- a/extension/base/src/jsMain/kotlin/browser/events/Event.kt +++ /dev/null @@ -1,122 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("events") - -package browser.events - -/** - * An object which allows the addition and removal of listeners for a Chrome event. - */ -public external interface Event { - /** - * Registers an event listener callback to an event. - * - * @param callback Called when an event occurs. The parameters of this function depend on the type - * of event. - */ - public fun addListener(callback: (T) -> R): Unit - - /** - * Registers an event listener callback to an event. - * - * @param callback Called when an event occurs. The parameters of this function depend on the type - * of event. - */ - public fun addListener(callback: (T) -> R, vararg params: Any?): Unit - - /** - * Deregisters an event listener callback from an event. - * - * @param callback Listener that shall be unregistered. - */ - public fun removeListener(callback: (T) -> R): Unit - - /** - * @param callback Listener whose registration status shall be tested. - * @return True if callback is registered to the event. - */ - public fun hasListener(callback: (T) -> R): Boolean - - /** - * @return True if any event listeners are registered to the event. - */ - public fun hasListeners(): Boolean - - /** - * Registers rules to handle events. - * - * @param eventName Name of the event this function affects. - * @param webViewInstanceId If provided, this is an integer that uniquely identfies the - * associated with this function call. - * @param rules Rules to be registered. These do not replace previously registered rules. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun addRules( - eventName: String? = definedExternally, - webViewInstanceId: Int? = definedExternally, - rules: Array>, - callback: ((Array>) -> Unit)? = definedExternally, - ): Unit - - /** - * Registers rules to handle events. - * - * @param rules Rules to be registered. These do not replace previously registered rules. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun addRules(rules: Array>, callback: ((Array>) -> Unit)? = - definedExternally): Unit - - /** - * Returns currently registered rules. - * - * @param eventName Name of the event this function affects. - * @param webViewInstanceId If provided, this is an integer that uniquely identfies the - * associated with this function call. - * @param ruleIdentifiers If an array is passed, only rules with identifiers contained in this - * array are returned. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun getRules( - eventName: String? = definedExternally, - webViewInstanceId: Int? = definedExternally, - ruleIdentifiers: Array? = definedExternally, - callback: (Array>) -> Unit, - ): Unit - - /** - * Returns currently registered rules. - * - * @param ruleIdentifiers If an array is passed, only rules with identifiers contained in this - * array are returned. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun getRules(ruleIdentifiers: Array? = definedExternally, - callback: (Array>) -> Unit): Unit - - /** - * Unregisters currently registered rules. - * - * @param eventName Name of the event this function affects. - * @param webViewInstanceId If provided, this is an integer that uniquely identfies the - * associated with this function call. - * @param ruleIdentifiers If an array is passed, only rules with identifiers contained in this - * array are returned. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun removeRules( - eventName: String? = definedExternally, - webViewInstanceId: Int? = definedExternally, - ruleIdentifiers: Array? = definedExternally, - callback: ((Array>) -> Unit)? = definedExternally, - ): Unit - - /** - * Unregisters currently registered rules. - * - * @param ruleIdentifiers If an array is passed, only rules with identifiers contained in this - * array are returned. - * @param callback Rules that were registered, the optional parameters are filled with values. - */ - public fun removeRules(ruleIdentifiers: Array? = definedExternally, - callback: ((Array>) -> Unit)? = definedExternally): Unit -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/events/Rule.kt b/extension/base/src/jsMain/kotlin/browser/events/Rule.kt deleted file mode 100644 index 5262894f..00000000 --- a/extension/base/src/jsMain/kotlin/browser/events/Rule.kt +++ /dev/null @@ -1,34 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("events") - -package browser.events - -/** - * Description of a declarative rule for handling events. - */ -public external interface Rule { - /** - * Optional identifier that allows referencing this rule. - */ - public var id: String? - - /** - * Tags can be used to annotate rules and perform operations on sets of rules. - */ - public var tags: Array? - - /** - * List of conditions that can trigger the actions. - */ - public var conditions: Array - - /** - * List of actions that are triggered if one of the conditions is fulfilled. - */ - public var actions: Array - - /** - * Optional identifier that allows referencing this rule. - */ - public var priority: Int? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/events/UrlFilter.kt b/extension/base/src/jsMain/kotlin/browser/events/UrlFilter.kt deleted file mode 100644 index a13cdcb9..00000000 --- a/extension/base/src/jsMain/kotlin/browser/events/UrlFilter.kt +++ /dev/null @@ -1,126 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("events") - -package browser.events - -/** - * Filters URLs for various criteria. See event filtering. All - * criteria are case sensitive. - */ -public external interface UrlFilter { - /** - * Matches if the host name of the URL contains a specified string. To test whether a host name - * component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and - * 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, - * hostContains can be used to match against component suffix ('foo.') and to exactly match against - * components ('.foo.'). Suffix- and exact-matching for the last components need to be done - * separately using hostSuffix, because no implicit dot is added at the end of the host name. - */ - public var hostContains: String? - - /** - * Matches if the host name of the URL is equal to a specified string. - */ - public var hostEquals: String? - - /** - * Matches if the host name of the URL starts with a specified string. - */ - public var hostPrefix: String? - - /** - * Matches if the host name of the URL ends with a specified string. - */ - public var hostSuffix: String? - - /** - * Matches if the path segment of the URL contains a specified string. - */ - public var pathContains: String? - - /** - * Matches if the path segment of the URL is equal to a specified string. - */ - public var pathEquals: String? - - /** - * Matches if the path segment of the URL starts with a specified string. - */ - public var pathPrefix: String? - - /** - * Matches if the path segment of the URL ends with a specified string. - */ - public var pathSuffix: String? - - /** - * Matches if the query segment of the URL contains a specified string. - */ - public var queryContains: String? - - /** - * Matches if the query segment of the URL is equal to a specified string. - */ - public var queryEquals: String? - - /** - * Matches if the query segment of the URL starts with a specified string. - */ - public var queryPrefix: String? - - /** - * Matches if the query segment of the URL ends with a specified string. - */ - public var querySuffix: String? - - /** - * Matches if the URL (without fragment identifier) contains a specified string. Port numbers are - * stripped from the URL if they match the default port number. - */ - public var urlContains: String? - - /** - * Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers - * are stripped from the URL if they match the default port number. - */ - public var urlEquals: String? - - /** - * Matches if the URL (without fragment identifier) matches a specified regular expression. Port - * numbers are stripped from the URL if they match the default port number. The regular expressions - * use the RE2 syntax. - */ - public var urlMatches: String? - - /** - * Matches if the URL without query segment and fragment identifier matches a specified regular - * expression. Port numbers are stripped from the URL if they match the default port number. The - * regular expressions use the RE2 - * syntax. - */ - public var originAndPathMatches: String? - - /** - * Matches if the URL (without fragment identifier) starts with a specified string. Port numbers - * are stripped from the URL if they match the default port number. - */ - public var urlPrefix: String? - - /** - * Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are - * stripped from the URL if they match the default port number. - */ - public var urlSuffix: String? - - /** - * Matches if the scheme of the URL is equal to any of the schemes specified in the array. - */ - public var schemes: Array? - - /** - * Matches if the port of the URL is contained in any of the specified port lists. For example - * [80, 443, [1000, 1200]] matches all requests on port 80, 443 and in the range - * 1000-1200. - */ - public var ports: Array? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/runtime/Import.kt b/extension/base/src/jsMain/kotlin/browser/runtime/Import.kt deleted file mode 100644 index 7c981379..00000000 --- a/extension/base/src/jsMain/kotlin/browser/runtime/Import.kt +++ /dev/null @@ -1,32 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("runtime") - -package browser.runtime - -import browser.events.Event -import kotlin.js.Promise - -/** - * Sends a single message to event listeners within your extension/app or a different extension/app. - * Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If - * sending to your extension, the $(ref:runtime.onMessage) event will be fired in every frame of your - * extension (except for the sender's frame), or $(ref:runtime.onMessageExternal), if a different - * extension. Note that extensions cannot send messages to content scripts using this method. To send - * messages to content scripts, use $(ref:tabs.sendMessage). - * - * @param extensionId The ID of the extension/app to send the message to. If omitted, the message - * will be sent to your own extension/app. Required if sending messages from a web page for web messaging. - * @param message The message to send. This message should be a JSON-ifiable object. - */ -public external fun sendMessage( - extensionId: String? = definedExternally, - message: Any, - options: SendMessageOptions? = definedExternally -): Promise? - -/** - * Fired when a message is sent from either an extension process (by $(ref:runtime.sendMessage)) or - * a content script (by $(ref:tabs.sendMessage)). - */ -public external val onMessage: Event = definedExternally \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/runtime/MessageSender.kt b/extension/base/src/jsMain/kotlin/browser/runtime/MessageSender.kt deleted file mode 100644 index 28d32e2c..00000000 --- a/extension/base/src/jsMain/kotlin/browser/runtime/MessageSender.kt +++ /dev/null @@ -1,76 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("runtime") - -package browser.runtime - -import browser.tabs.Tab - -/** - * An object containing information about the script context that sent a message or request. - */ -public external interface MessageSender { - /** - * The $(ref:tabs.Tab) which opened the connection, if any. This property will - * only be present when the connection was opened from a tab (including content - * scripts), and only if the receiver is an extension, not an app. - */ - public var tab: Tab? - - /** - * The frame that opened the connection. 0 for top-level - * frames, positive for child frames. This will only be set when tab is set. - */ - public var frameId: Int? - - /** - * The guest process id of the requesting webview, if available. Only available for component - * extensions. - */ - public var guestProcessId: Int? - - /** - * The guest render frame routing id of the requesting webview, if available. Only available for - * component extensions. - */ - public var guestRenderFrameRoutingId: Int? - - /** - * The ID of the extension or app that opened the connection, if any. - */ - public var id: String? - - /** - * The URL of the page or frame that opened the connection. If the sender is in an iframe, it will - * be iframe's URL not the URL of the page which hosts it. - */ - public var url: String? - - /** - * The name of the native application that opened the connection, if any. - */ - public var nativeApplication: String? - - /** - * The TLS channel ID of the page or frame that opened the connection, if requested by the - * extension or app, and if available. - */ - public var tlsChannelId: String? - - /** - * The origin of the page or frame that opened the connection. It can vary from the url property - * (e.g., about:blank) or can be opaque (e.g., sandboxed iframes). This is useful for identifying if - * the origin can be trusted if we can't immediately tell from the URL. - */ - public var origin: String? - - /** - * A UUID of the document that opened the connection. - */ - public var documentId: String? - - /** - * The lifecycle the document that opened the connection is in at the time the port was created. - * Note that the lifecycle state of the document may have changed since port creation. - */ - public var documentLifecycle: String? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/runtime/OnMessageListener.kt b/extension/base/src/jsMain/kotlin/browser/runtime/OnMessageListener.kt deleted file mode 100644 index 765f80c8..00000000 --- a/extension/base/src/jsMain/kotlin/browser/runtime/OnMessageListener.kt +++ /dev/null @@ -1,27 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("runtime") - -package browser.runtime - -/** - * Fired when a message is sent from either an extension process (by $(ref:runtime.sendMessage)) or - * a content script (by $(ref:tabs.sendMessage)). - */ -public external interface OnMessageListener { - /** - * The message sent by the calling script. - */ - public var message: Any? - - public var sender: MessageSender - - /** - * Function to call (at most once) when you have a response. The argument should be any - * JSON-ifiable object. If you have more than one onMessage listener in the same - * document, then only one may send a response. This function becomes invalid when the event listener - * returns, unless you return true from the event listener to indicate you wish to - * send a response asynchronously (this will keep the message channel open to the other end until - * sendResponse is called). - */ - public fun sendResponse(): Nothing -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/runtime/SendMessageOptions.kt b/extension/base/src/jsMain/kotlin/browser/runtime/SendMessageOptions.kt deleted file mode 100644 index d4aa85d1..00000000 --- a/extension/base/src/jsMain/kotlin/browser/runtime/SendMessageOptions.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("runtime") - -package browser.runtime - -external interface SendMessageOptions { - - /** - * Whether the TLS channel ID will be passed into onMessageExternal for processes that are - * listening for the connection event. - */ - public var includeTlsChannelId: Boolean? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfo.kt b/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfo.kt deleted file mode 100644 index e7d5ef27..00000000 --- a/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfo.kt +++ /dev/null @@ -1,28 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("tabs") - -package browser.tabs - -/** - * The tab's muted state and the reason for the last state change. - */ -public external interface MutedInfo { - /** - * Whether the tab is muted (prevented from playing sound). The tab may be muted even if it has - * not played or is not currently playing sound. Equivalent to whether the 'muted' audio indicator is - * showing. - */ - public var muted: Boolean - - /** - * The reason the tab was muted or unmuted. Not set if the tab's mute state has never been - * changed. - */ - public var reason: MutedInfoReason? - - /** - * The ID of the extension that changed the muted state. Not set if an extension was not the - * reason the muted state last changed. - */ - public var extensionId: String? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfoReason.kt b/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfoReason.kt deleted file mode 100644 index 5279571d..00000000 --- a/extension/base/src/jsMain/kotlin/browser/tabs/MutedInfoReason.kt +++ /dev/null @@ -1,24 +0,0 @@ -package browser.tabs - -/** - * An event that caused a muted state change. - */ -public enum class MutedInfoReason( - private val `value`: String, -) { - /** - * A user input action set the muted state. - */ - user("user"), - /** - * Tab capture was started, forcing a muted state change. - */ - capture("capture"), - /** - * An extension, identified by the extensionId field, set the muted state. - */ - extension("extension"), - ; - - public override fun toString(): String = value -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/tabs/Tab.kt b/extension/base/src/jsMain/kotlin/browser/tabs/Tab.kt deleted file mode 100644 index a0bf3c84..00000000 --- a/extension/base/src/jsMain/kotlin/browser/tabs/Tab.kt +++ /dev/null @@ -1,133 +0,0 @@ -@file:JsModule("webextension-polyfill") -@file:JsQualifier("tabs") - -package browser.tabs - -public external interface Tab { - /** - * The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a tab - * may not be assigned an ID; for example, when querying foreign tabs using the $(ref:sessions) API, - * in which case a session ID may be present. Tab ID can also be set to - * chrome.tabs.TAB_ID_NONE for apps and devtools windows. - */ - public var id: Int? - - /** - * The zero-based index of the tab within its window. - */ - public var index: Int - - /** - * The ID of the group that the tab belongs to. - */ - public var groupId: Int - - /** - * The ID of the window that contains the tab. - */ - public var windowId: Int - - /** - * The ID of the tab that opened this tab, if any. This property is only present if the opener tab - * still exists. - */ - public var openerTabId: Int? - - /** - * Whether the tab is selected. - */ - @Deprecated( - message = "Please use ${'$'}(ref:tabs.Tab.highlighted).", - level = DeprecationLevel.WARNING, - ) - public var selected: Boolean - - /** - * Whether the tab is highlighted. - */ - public var highlighted: Boolean - - /** - * Whether the tab is active in its window. Does not necessarily mean the window is focused. - */ - public var active: Boolean - - /** - * Whether the tab is pinned. - */ - public var pinned: Boolean - - /** - * Whether the tab has produced sound over the past couple of seconds (but it might not be heard - * if also muted). Equivalent to whether the 'speaker audio' indicator is showing. - */ - public var audible: Boolean? - - /** - * Whether the tab is discarded. A discarded tab is one whose content has been unloaded from - * memory, but is still visible in the tab strip. Its content is reloaded the next time it is - * activated. - */ - public var discarded: Boolean - - /** - * Whether the tab can be discarded automatically by the browser when resources are low. - */ - public var autoDiscardable: Boolean - - /** - * The tab's muted state and the reason for the last state change. - */ - public var mutedInfo: MutedInfo? - - /** - * The last committed URL of the main frame of the tab. This property is only present if the - * extension's manifest includes the "tabs" permission and may be an empty string if the - * tab has not yet committed. See also $(ref:Tab.pendingUrl). - */ - public var url: String? - - /** - * The URL the tab is navigating to, before it has committed. This property is only present if the - * extension's manifest includes the "tabs" permission and there is a pending - * navigation. - */ - public var pendingUrl: String? - - /** - * The title of the tab. This property is only present if the extension's manifest includes the - * "tabs" permission. - */ - public var title: String? - - /** - * The URL of the tab's favicon. This property is only present if the extension's manifest - * includes the "tabs" permission. It may also be an empty string if the tab is loading. - */ - public var favIconUrl: String? - - /** - * The tab's loading status. - */ - public var status: TabStatus? - - /** - * Whether the tab is in an incognito window. - */ - public var incognito: Boolean - - /** - * The width of the tab in pixels. - */ - public var width: Int? - - /** - * The height of the tab in pixels. - */ - public var height: Int? - - /** - * The session ID used to uniquely identify a tab obtained from the $(ref:sessions) API. - */ - public var sessionId: String? -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/browser/tabs/TabStatus.kt b/extension/base/src/jsMain/kotlin/browser/tabs/TabStatus.kt deleted file mode 100644 index 21402c9c..00000000 --- a/extension/base/src/jsMain/kotlin/browser/tabs/TabStatus.kt +++ /dev/null @@ -1,15 +0,0 @@ -package browser.tabs - -/** - * The tab's loading status. - */ -public enum class TabStatus( - private val `value`: String, -) { - unloaded("unloaded"), - loading("loading"), - complete("complete"), - ; - - public override fun toString(): String = value -} \ No newline at end of file diff --git a/extension/base/src/jsMain/kotlin/common/ExtendAny.kt b/extension/base/src/jsMain/kotlin/common/ExtendAny.kt deleted file mode 100644 index 2c67502d..00000000 --- a/extension/base/src/jsMain/kotlin/common/ExtendAny.kt +++ /dev/null @@ -1,75 +0,0 @@ -package common - -import kotlinx.dom.removeClass -import org.w3c.dom.* -import kotlin.js.Promise -import kotlin.collections.isNullOrEmpty as defaultNullOrEmpty - -fun Promise?.collect(listener: (T) -> Unit) = this?.then(listener) - -fun Any?.isNullOrEmpty(): Boolean = when (this) { - null -> true - is Collection<*> -> this.defaultNullOrEmpty() - is Array<*> -> this.defaultNullOrEmpty() - else -> { - this == undefined || ((this is String || jsTypeOf(this).equals("string", true)) && (this.toString().isEmpty() || this.toString().isBlank())) - } -} - -inline fun HTMLCollection?.forEach(listener: (Element?) -> Unit) { - if (this == null) { - return - } - for (i in 0 until this.length) { - listener(this[i]) - } -} - -inline fun HTMLCollection?.forEachNotNull(listener: (Element) -> Unit) { - if (this == null) { - return - } - for (i in 0 until this.length) { - this[i]?.let(listener) - } -} - -fun Document?.onReady(listener: () -> Unit) { - if (this == null) { - return - } - - if (this.readyState == DocumentReadyState.COMPLETE) { - listener() - return - } - - this.onreadystatechange = { - if (this.readyState == DocumentReadyState.COMPLETE) { - listener() - } - } -} - -fun Element?.removeAllClasses() { - if (this == null) { - return - } - - val allClasses = this.classList - for (i in 0 until allClasses.length) { - this.removeClass(allClasses[i] ?: String()) - } -} - -inline fun objectOf( - jsonObject: I = js("new Object()").unsafeCast(), - writer: I.() -> Unit -): I { - writer(jsonObject) - return jsonObject -} - -fun Element.insertAfter(newNode: Node) { - this.parentNode?.insertBefore(newNode, this.nextSibling) -} \ No newline at end of file diff --git a/extension/build.gradle.kts b/extension/build.gradle.kts deleted file mode 100644 index 02ea3f09..00000000 --- a/extension/build.gradle.kts +++ /dev/null @@ -1,124 +0,0 @@ -import groovy.json.JsonBuilder -import groovy.json.JsonSlurper - -plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.serialization) - alias(libs.plugins.ktorfit) -} - -val version = appVersion -val artifact = VersionCatalog.artifactName("extension") -group = artifact - -kotlin { - js(IR) { - browser() - binaries.executable() - } - - sourceSets { - jsMain.get().dependencies { - runtimeOnly(npm("webextension-polyfill", "0.10.0")) - } - } -} - -tasks { - val extensionFolder = File(rootProject.layout.buildDirectory.get().asFile, "extension") - val resourcesFolder = File(projectDir, "src/jsMain/resources") - val releaseFolder = File(rootProject.layout.buildDirectory.get().asFile, "release/main/extension") - val commonShared = File(rootProject.project("app").project("shared").projectDir, "src/commonMain") - val iconsFolder = File(commonShared, "resources/MR/assets/png") - var firefox = false - - val buildAndCopy = register("buildAndCopy") { - dependsOn( - project("content").tasks.build, - project("background").tasks.build, - assemble - ) - - doLast { - copy { - from(File(layout.buildDirectory.get().asFile, "distributions")) { - include("*.js") - } - into(extensionFolder) - } - - copy { - from(File(resourcesFolder, "manifest.json")) - into(extensionFolder) - - from(iconsFolder) { - include("launcher_*.png") - into("icons") - } - } - - copy { - val buildDirs = listOf( - rootProject.layout.buildDirectory.get().asFile, - layout.buildDirectory.get().asFile - ) - - from(buildDirs.map { File(it, "js/node_modules/webextension-polyfill/dist") }) { - include("browser-polyfill.min.js") - include("browser-polyfill.min.js.map") - } - into(extensionFolder) - } - - jsonObjectAsMap( - JsonSlurper().parse(File(extensionFolder, "manifest.json")) - )?.toMutableMap()?.let { json -> - if (json.containsKey("version")) { - json["version"] = version - } - if (firefox) { - json["background"] = mapOf("scripts" to listOf("background.js")) - json["browser_specific_settings"] = mapOf("gecko" to mapOf("id" to "burningseries@datlag.dev")) - - val permissions = ((json["permissions"] as? List) ?: listOf("storage")).toMutableList() - permissions.remove("background") - permissions.add("activeTab") - json["permissions"] = permissions - } - - File(extensionFolder, "manifest.json").writeText(JsonBuilder(json).toPrettyString()) - } - } - } - - register("packChromium") { - firefox = false - dependsOn(buildAndCopy) - - mkdir(releaseFolder) - from(extensionFolder) - archiveBaseName.set("Chromium-$version") - destinationDirectory.set(releaseFolder) - } - register("packFirefox") { - firefox = true - dependsOn(buildAndCopy) - - mkdir(releaseFolder) - from(extensionFolder) - archiveBaseName.set("Firefox-$version") - archiveExtension.set("xpi") - destinationDirectory.set(releaseFolder) - } -} - -fun jsonObjectAsMap(data: Any?): Map? { - if (data == null) { - return null - } - - val map = data as? Map<*, *> ?: return null - return runCatching { - map as Map - }.getOrNull() -} \ No newline at end of file diff --git a/extension/content/build.gradle.kts b/extension/content/build.gradle.kts deleted file mode 100644 index 49935d01..00000000 --- a/extension/content/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - alias(libs.plugins.multiplatform) - alias(libs.plugins.serialization) -} - -kotlin { - js(IR) { - browser { - binaries.executable() - webpackTask { - mainOutputFileName = "content_script.js" - sourceMaps = false - } - distribution { - outputDirectory.set(parentBuildDir("distributions")) - } - } - } - - sourceSets { - jsMain.get().dependencies { - implementation(parent?.project("base") ?: rootProject.project("extension:base")) - implementation(rootProject.project("model")) - } - } -} \ No newline at end of file diff --git a/extension/content/src/jsMain/kotlin/content.kt b/extension/content/src/jsMain/kotlin/content.kt deleted file mode 100644 index 69554ee0..00000000 --- a/extension/content/src/jsMain/kotlin/content.kt +++ /dev/null @@ -1,180 +0,0 @@ -import common.collect -import common.forEachNotNull -import common.isNullOrEmpty -import common.onReady -import dev.datlag.burningseries.model.ExtensionMessage -import kotlinx.browser.document -import kotlinx.browser.window -import kotlinx.dom.hasClass -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.w3c.dom.Element -import org.w3c.dom.MutationObserver -import org.w3c.dom.MutationObserverInit -import org.w3c.dom.get - -@OptIn(ExperimentalStdlibApi::class) -fun main() { - document.onReady { - val href = document.URL.ifBlank { document.location?.href } ?: document.URL - - if (document.getElementsByClassName("episodes").length <= 0) { - checkEpisode(href) - } else { - checkSeason(href) - } - } -} - -private fun checkSeason(docHref: String) { - fun checkHoster(hoster: Element) { - hoster.getAttribute("href")?.let { href -> - entryExists(href) { exists -> - if (exists) { - val (background, content) = getThemeColor(docHref, href) - val style = buildString { - append("background-color: ${background.asHexColor()};") - append("color: ${content.asHexColor()};") - append("border: 3px solid ${background.asHexColor()};") - append("border-radius: 3px;") - } - hoster.setAttribute("style", style) - } - } - } - } - - val episodes = document.getElementsByClassName("episodes")[0]?.getElementsByTagName("tr") - episodes.forEachNotNull { episode -> - val hosters = episode.getElementsByTagName("td")[2]?.getElementsByTagName("a") - hosters.forEachNotNull { hoster -> - checkHoster(hoster) - } - } -} - -@OptIn(ExperimentalStdlibApi::class) -private fun checkEpisode(docHref: String) { - fun hosterApplyTheme(hoster: Element, href: String) { - val (background, content) = getThemeColor(docHref, href) - hoster.setAttribute("style", "background-color: ${background.asHexColor()}; color: ${content.asHexColor()}") - } - - fun checkHoster(hoster: Element) { - if (hoster.hasClass("active")) { - return - } - hoster.getElementsByTagName("a")[0]?.getAttribute("href")?.let { href -> - entryExists(href) { exists -> - if (exists) { - hosterApplyTheme(hoster, href) - } - } - } - } - - fun observeActivation(hoster: Element) { - if (!hoster.hasClass("active")) { - return - } - - val observer = MutationObserver { mutations, _ -> - for (mutation in mutations) { - if (mutation.addedNodes.length <= 0) { - continue - } - - for (i in 0 until mutation.addedNodes.length) { - val node = mutation.addedNodes[i] - - if (!node.isNullOrEmpty() && !node.asDynamic().tagName.unsafeCast().isNullOrEmpty()) { - val tagName = node.asDynamic().tagName.unsafeCast() - val url = if (tagName!!.equals("a", true)) { - (node as? Element)?.getAttribute("href") - } else if (tagName.equals("iframe", true)) { - (node as? Element)?.getAttribute("src") - } else { - null - } - val href = hoster.getElementsByTagName("a")[0]?.getAttribute("href") - - if (!url.isNullOrEmpty() && !href.isNullOrEmpty()) { - entrySave(href!!, url!!) { saved -> - if (saved) { - hosterApplyTheme(hoster, href) - } - } - } - } - } - } - } - - document.getElementsByClassName("hoster-player")[0]?.let { node -> - observer.observe( - node, - MutationObserverInit( - childList = true, - subtree = true, - attributes = false, - characterData = false - ) - ) - } - } - - val hosterArea = document.getElementsByClassName("hoster-tabs") - hosterArea.forEachNotNull { area -> - val hosters = area.getElementsByTagName("li") - hosters.forEachNotNull { hoster -> - checkHoster(hoster) - observeActivation(hoster) - } - } -} - -private val json = Json - -private fun entrySave(href: String, url: String, callback: (Boolean) -> Unit) { - browser.runtime.sendMessage( - message = json.encodeToString(ExtensionMessage( - set = true, - href = href, - url = url - )) - ).collect { - callback((it as? Boolean) ?: false) - } -} - -private fun entryExists(href: String, callback: (Boolean) -> Unit) { - browser.runtime.sendMessage( - message = json.encodeToString(ExtensionMessage( - set = false, - href = href - )) - ).collect { - callback((it as? Boolean) ?: false) - } -} - -private fun getThemeColor(docHref: String, href: String): Pair { - val isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches - val (fallbackBackground, fallbackContent) = if (isDarkMode) { - 0xFF00497f.toInt() to 0xFFd2e4ff.toInt() - } else { - 0xFFd2e4ff.toInt() to 0xFF001c37.toInt() - } - return fallbackBackground to fallbackContent -} - -@OptIn(ExperimentalStdlibApi::class) -private fun Int.asHexColor(): String { - var hexColor = this.toHexString() - hexColor = if (hexColor.length == 8) { - hexColor.drop(2) - } else { - hexColor - } - return "#$hexColor" -} \ No newline at end of file diff --git a/extension/src/jsMain/resources/manifest.json b/extension/src/jsMain/resources/manifest.json deleted file mode 100644 index b0d1c6da..00000000 --- a/extension/src/jsMain/resources/manifest.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "manifest_version": 3, - - "name": "Burning-Series Activate", - "description": "Burning-Series Activate is an extension to activate episodes for the app in your browser.", - "version": "0.0.0", - - "action": { - "default_icon": "icons/launcher_128.png" - }, - - "icons": { - "16": "icons/launcher_16.png", - "32": "icons/launcher_32.png", - "48": "icons/launcher_48.png", - "64": "icons/launcher_64.png", - "96": "icons/launcher_96.png", - "128": "icons/launcher_128.png" - }, - - "content_scripts": [ - { - "matches": [ - "*://*.bs.to/serie/*", - "*://*.burningseries.co/serie/*", - "*://*.burningseries.sx/serie/*", - "*://*.burningseries.ac/serie/*", - "*://*.burningseries.vc/serie/*", - "*://*.burningseries.cx/serie/*", - "*://*.burningseries.nz/serie/*", - "*://*.burningseries.se/serie/*", - "*://*.burningseries.tw/serie/*" - ], - "js": [ - "browser-polyfill.min.js", - "content_script.js" - ] - } - ], - - "background": { - "service_worker": "background.js" - }, - - "permissions": [ - "background" - ], - - "host_permissions": [ - "https://jsonbase.com/*", - "https://*.googleapis.com/*" - ] -} \ No newline at end of file diff --git a/firebase/build.gradle.kts b/firebase/build.gradle.kts new file mode 100644 index 00000000..5957ecf1 --- /dev/null +++ b/firebase/build.gradle.kts @@ -0,0 +1,82 @@ +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.serialization) +} + +kotlin { + jvm() + androidTarget() + + iosX64() + iosArm64() + iosSimulatorArm64() + + macosX64() + macosArm64() + + linuxX64() + linuxArm64() + + mingwX64() + + js(IR) { + nodejs() + browser() + binaries.executable() + } + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + implementation(libs.tooling) + implementation(project(":model")) + } + + val firebaseMain by creating { + dependsOn(commonMain.get()) + + androidMain.orNull?.dependsOn(this) + jvmMain.orNull?.dependsOn(this) + iosMain.orNull?.dependsOn(this) + jsMain.orNull?.dependsOn(this) + + dependencies { + implementation(libs.firebase.auth) + implementation(libs.firebase.store) + } + } + + androidMain.dependencies { + // implementation(libs.firebase.android) + // implementation(libs.firebase.android.analytics) + // implementation(libs.firebase.android.auth) + // implementation(libs.firebase.android.crashlytics) + + implementation(libs.firebase.crashlytics) + } + jvmMain.dependencies { + implementation(libs.firebase.java) + } + + val iosMain by getting { + dependencies { + implementation(libs.firebase.crashlytics) + } + } + } +} + +android { + compileSdk = 34 + namespace = "dev.datlag.burningseries.firebase" + + defaultConfig { + minSdk = 23 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } +} \ No newline at end of file diff --git a/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.android.kt b/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.android.kt new file mode 100644 index 00000000..573f3e9a --- /dev/null +++ b/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.android.kt @@ -0,0 +1,65 @@ +package dev.datlag.burningseries.firebase + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.crashlytics.crashlytics + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: String +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Boolean +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Int +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Long +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Float +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Double +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsLog(app: FirebaseApp, throwable: Throwable?) { + throwable?.let { + Firebase.crashlytics(app).recordException(it) + } +} + +internal actual fun crashlyticsLog(app: FirebaseApp, message: String?) { + message?.ifBlank { null }?.let { + Firebase.crashlytics(app).log(it) + } +} \ No newline at end of file diff --git a/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt b/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt new file mode 100644 index 00000000..3918246b --- /dev/null +++ b/firebase/src/androidMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt @@ -0,0 +1,39 @@ +package dev.datlag.burningseries.firebase + +import android.content.Context +import android.util.Log +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.initialize + +fun FirebaseFactory.Companion.initialize( + context: Context, + projectId: String?, + applicationId: String, + apiKey: String, + localLogger: FirebaseFactory.Crashlytics.LocalLogger? +): FirebaseFactory { + return CommonFirebase( + app = Firebase.initialize( + context = context, + options = FirebaseOptions( + projectId = projectId, + applicationId = applicationId, + apiKey = apiKey + ) + ), + localLogger = localLogger ?: object : FirebaseFactory.Crashlytics.LocalLogger { + override fun warn(message: String?) { + message?.let { Log.w(null, it) } + } + + override fun error(throwable: Throwable?) { + throwable?.let { Log.e(null, null, throwable) } + } + + override fun error(message: String?) { + message?.let { Log.e(null, it) } + } + } + ) +} \ No newline at end of file diff --git a/firebase/src/commonMain/kotlin/dev/datlag/burningseries/firebase/FirebaseFactory.kt b/firebase/src/commonMain/kotlin/dev/datlag/burningseries/firebase/FirebaseFactory.kt new file mode 100644 index 00000000..7f301484 --- /dev/null +++ b/firebase/src/commonMain/kotlin/dev/datlag/burningseries/firebase/FirebaseFactory.kt @@ -0,0 +1,59 @@ +package dev.datlag.burningseries.firebase + +import dev.datlag.burningseries.model.HosterScraping + +interface FirebaseFactory { + + val auth: Auth + val crashlytics: Crashlytics + val store: Store + + data object Empty : FirebaseFactory, Auth, Crashlytics, Store { + override val auth: Auth = this + override val crashlytics: Crashlytics = this + override val store: Store = this + } + + interface Auth { + val isSignedIn: Boolean + get() = false + + suspend fun signInAnonymously(): Boolean = false + suspend fun signOut() { } + + suspend fun delete() { } + + companion object + } + + interface Store { + suspend fun streams(hrefList: List): List = emptyList() + suspend fun addStream(data: HosterScraping.FireStore): Boolean = false + + companion object + } + + interface Crashlytics { + val localLogger: LocalLogger? + get() = null + + fun customKey(key: String, value: String) { } + fun customKey(key: String, value: Boolean) { } + fun customKey(key: String, value: Int) { } + fun customKey(key: String, value: Long) { } + fun customKey(key: String, value: Float) { } + fun customKey(key: String, value: Double) { } + fun log(throwable: Throwable?) { } + fun log(message: String?) { } + + interface LocalLogger { + fun warn(message: String?) + fun error(throwable: Throwable?) + fun error(message: String?) + } + + companion object + } + + companion object +} \ No newline at end of file diff --git a/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/CommonFirebase.kt b/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/CommonFirebase.kt new file mode 100644 index 00000000..5d36a19d --- /dev/null +++ b/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/CommonFirebase.kt @@ -0,0 +1,101 @@ +package dev.datlag.burningseries.firebase + +import dev.datlag.burningseries.model.HosterScraping +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.auth.auth +import dev.gitlive.firebase.firestore.FirebaseFirestore +import dev.gitlive.firebase.firestore.firestore +import dev.gitlive.firebase.firestore.where + +open class CommonFirebase( + private val app: FirebaseApp, + localLogger: FirebaseFactory.Crashlytics.LocalLogger? +) : FirebaseFactory { + + override val auth: FirebaseFactory.Auth = Auth(app) + override val crashlytics: FirebaseFactory.Crashlytics = Crashlytics(app, localLogger) + override val store: FirebaseFactory.Store = Store(app) + + data class Auth( + private val app: FirebaseApp + ) : FirebaseFactory.Auth { + + override val isSignedIn: Boolean + get() = Firebase.auth(app).currentUser != null + + override suspend fun signInAnonymously(): Boolean { + return Firebase.auth(app).signInAnonymously().user != null + } + + override suspend fun signOut() { + Firebase.auth(app).signOut() + } + + override suspend fun delete() { + Firebase.auth(app).currentUser?.delete() + + signOut() + } + } + + data class Store( + private val app: FirebaseApp + ) : FirebaseFactory.Store { + override suspend fun streams(hrefList: List): List { + return Firebase.firestore(app).collection("stream").where { + all( + *listOfNotNull( + hrefList.let { "id" inArray it } + ).toTypedArray() + ) + }.get().documents.map { + it.get("url") + } + } + + override suspend fun addStream(data: HosterScraping.FireStore): Boolean { + val fireStore = Firebase.firestore(app) + val document = fireStore.collection("stream").where { + "id" equalTo data.id + }.get().documents.firstOrNull()?.reference?: fireStore.collection("stream").document + + fireStore.runTransaction { + set(document, data = data, merge = true) + } + return true + } + } + + data class Crashlytics( + private val app: FirebaseApp, + override val localLogger: FirebaseFactory.Crashlytics.LocalLogger? + ) : FirebaseFactory.Crashlytics { + override fun customKey(key: String, value: String) { + crashlyticsCustomKey(app, key, value) + } + override fun customKey(key: String, value: Boolean) { + crashlyticsCustomKey(app, key, value) + } + override fun customKey(key: String, value: Int) { + crashlyticsCustomKey(app, key, value) + } + override fun customKey(key: String, value: Long) { + crashlyticsCustomKey(app, key, value) + } + override fun customKey(key: String, value: Float) { + crashlyticsCustomKey(app, key, value) + } + override fun customKey(key: String, value: Double) { + crashlyticsCustomKey(app, key, value) + } + override fun log(throwable: Throwable?) { + localLogger?.error(throwable) + crashlyticsLog(app, throwable) + } + override fun log(message: String?) { + localLogger?.warn(message) + crashlyticsLog(app, message) + } + } +} \ No newline at end of file diff --git a/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.kt b/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.kt new file mode 100644 index 00000000..4ad76dee --- /dev/null +++ b/firebase/src/firebaseMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.firebase + +import dev.gitlive.firebase.FirebaseApp + +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: String) +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Boolean) +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Int) +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Long) +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Float) +internal expect fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Double) +internal expect fun crashlyticsLog(app: FirebaseApp, throwable: Throwable?) +internal expect fun crashlyticsLog(app: FirebaseApp, message: String?) \ No newline at end of file diff --git a/firebase/src/iosMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.ios.kt b/firebase/src/iosMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.ios.kt new file mode 100644 index 00000000..6c87cdc3 --- /dev/null +++ b/firebase/src/iosMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.ios.kt @@ -0,0 +1,61 @@ +package dev.datlag.burningseries.firebase + +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseApp +import dev.gitlive.firebase.crashlytics.crashlytics + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: String +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Boolean +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey(app: FirebaseApp, key: String, value: Int) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Long +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Float +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Double +) { + Firebase.crashlytics(app).setCustomKey(key, value) +} + +internal actual fun crashlyticsLog(app: FirebaseApp, throwable: Throwable?) { + throwable?.let { + Firebase.crashlytics(app).recordException(it) + } +} + +internal actual fun crashlyticsLog(app: FirebaseApp, message: String?) { + message?.ifBlank { null }?.let { + Firebase.crashlytics(app).log(it) + } +} \ No newline at end of file diff --git a/firebase/src/jsMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.js.kt b/firebase/src/jsMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.js.kt new file mode 100644 index 00000000..541f0921 --- /dev/null +++ b/firebase/src/jsMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.js.kt @@ -0,0 +1,43 @@ +package dev.datlag.burningseries.firebase + +import dev.gitlive.firebase.FirebaseApp + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: String +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Boolean +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Int +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Long +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Float +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Double +) { } + +internal actual fun crashlyticsLog(app: FirebaseApp, throwable: Throwable?) { } + +internal actual fun crashlyticsLog(app: FirebaseApp, message: String?) { } \ No newline at end of file diff --git a/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.jvm.kt b/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.jvm.kt new file mode 100644 index 00000000..541f0921 --- /dev/null +++ b/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/Crashlytics.jvm.kt @@ -0,0 +1,43 @@ +package dev.datlag.burningseries.firebase + +import dev.gitlive.firebase.FirebaseApp + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: String +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Boolean +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Int +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Long +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Float +) { } + +internal actual fun crashlyticsCustomKey( + app: FirebaseApp, + key: String, + value: Double +) { } + +internal actual fun crashlyticsLog(app: FirebaseApp, throwable: Throwable?) { } + +internal actual fun crashlyticsLog(app: FirebaseApp, message: String?) { } \ No newline at end of file diff --git a/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt b/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt new file mode 100644 index 00000000..d5476938 --- /dev/null +++ b/firebase/src/jvmMain/kotlin/dev/datlag/burningseries/firebase/ExtendFirebase.kt @@ -0,0 +1,61 @@ +package dev.datlag.burningseries.firebase + +import android.app.Application +import com.google.firebase.FirebasePlatform +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.initialize +import java.util.logging.Logger + +fun FirebaseFactory.Companion.initialize( + projectId: String?, + applicationId: String, + apiKey: String, + localLogger: FirebaseFactory.Crashlytics.LocalLogger? +) : FirebaseFactory { + return CommonFirebase( + Firebase.initialize( + context = Application(), + options = FirebaseOptions( + projectId = projectId, + applicationId = applicationId, + apiKey = apiKey + ) + ), + localLogger ?: object : FirebaseFactory.Crashlytics.LocalLogger { + override fun warn(message: String?) { + Logger.getGlobal().warning(message) + } + + override fun error(message: String?) { + Logger.getGlobal().severe(message) + } + + override fun error(throwable: Throwable?) { + Logger.getGlobal().severe(throwable?.stackTraceToString() ?: throwable?.message) + } + } + ) +} + +fun FirebaseFactory.Companion.initializePlatform() { + FirebasePlatform.initializeFirebasePlatform(object: FirebasePlatform() { + val storage = mutableMapOf() + + override fun clear(key: String) { + storage.remove(key) + } + + override fun log(msg: String) { + println(msg) + } + + override fun retrieve(key: String): String? { + return storage[key] + } + + override fun store(key: String, value: String) { + storage[key] = value + } + }) +} \ No newline at end of file diff --git a/github/build.gradle.kts b/github/build.gradle.kts new file mode 100644 index 00000000..9dddb353 --- /dev/null +++ b/github/build.gradle.kts @@ -0,0 +1,66 @@ +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.apollo) + alias(libs.plugins.serialization) + alias(libs.plugins.ksp) +} + +val artifact = "dev.datlag.burningseries.github" + +apollo { + service("GitHub") { + packageName.set(artifact) + srcDir("src/commonMain/graphql") + schemaFiles.from(file("src/commonMain/graphql/schema.graphqls")) + } +} + +kotlin { + jvm() + androidTarget() + + iosX64() + iosArm64() + iosSimulatorArm64() + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + api(libs.apollo) + // implementation(libs.kache) + api(libs.flowredux) + // implementation(libs.datetime) + implementation(libs.serialization) + implementation(libs.tooling) + api(libs.immutable) + api(libs.ktorfit) + + implementation(project(":model")) + implementation(project(":firebase")) + } + } +} + +dependencies { + add("kspCommonMainMetadata", libs.ktorfit.ksp) + add("kspAndroid", libs.ktorfit.ksp) + add("kspJvm", libs.ktorfit.ksp) + add("kspIosX64", libs.ktorfit.ksp) + add("kspIosArm64", libs.ktorfit.ksp) + add("kspIosSimulatorArm64", libs.ktorfit.ksp) +} + +android { + compileSdk = 34 + namespace = artifact + + defaultConfig { + minSdk = 23 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } +} \ No newline at end of file diff --git a/github/src/commonMain/graphql/UserAndRelease.graphql b/github/src/commonMain/graphql/UserAndRelease.graphql new file mode 100644 index 00000000..960249b4 --- /dev/null +++ b/github/src/commonMain/graphql/UserAndRelease.graphql @@ -0,0 +1,28 @@ +query UserAndRelease($owner: String!, $repo: String!) { + user(login: $owner) { + viewerIsSponsoring + isViewer + } + repository(owner: $owner, name: $repo) { + latestRelease { + url, + tagName, + name, + isPrerelease, + isDraft, + releaseAssets(first: 20) { + nodes { + contentType, + downloadUrl, + name + } + } + } + viewerHasStarred + } + viewer { + avatarUrl(size: 300), + name, + login + } +} \ No newline at end of file diff --git a/github/src/commonMain/graphql/schema.graphqls b/github/src/commonMain/graphql/schema.graphqls new file mode 100644 index 00000000..0e129254 --- /dev/null +++ b/github/src/commonMain/graphql/schema.graphqls @@ -0,0 +1,52230 @@ +""" +Autogenerated input type of AbortQueuedMigrations +""" +input AbortQueuedMigrationsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that is running the migrations. + """ + ownerId: ID! +} + +""" +Autogenerated return type of AbortQueuedMigrations. +""" +type AbortQueuedMigrationsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Autogenerated input type of AbortRepositoryMigration +""" +input AbortRepositoryMigrationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the migration to be aborted. + """ + migrationId: ID! +} + +""" +Autogenerated return type of AbortRepositoryMigration. +""" +type AbortRepositoryMigrationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Autogenerated input type of AcceptEnterpriseAdministratorInvitation +""" +input AcceptEnterpriseAdministratorInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the invitation being accepted + """ + invitationId: ID! +} + +""" +Autogenerated return type of AcceptEnterpriseAdministratorInvitation. +""" +type AcceptEnterpriseAdministratorInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was accepted. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of accepting an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of AcceptEnterpriseMemberInvitation +""" +input AcceptEnterpriseMemberInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the invitation being accepted + """ + invitationId: ID! +} + +""" +Autogenerated return type of AcceptEnterpriseMemberInvitation. +""" +type AcceptEnterpriseMemberInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was accepted. + """ + invitation: EnterpriseMemberInvitation + + """ + A message confirming the result of accepting an unaffiliated member invitation. + """ + message: String +} + +""" +Autogenerated input type of AcceptTopicSuggestion +""" +input AcceptTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + + **Upcoming Change on 2024-04-01 UTC** + **Description:** `repositoryId` will be removed. + **Reason:** Suggested topics are no longer supported + """ + repositoryId: ID + + """ + The name of the suggested topic. + + **Upcoming Change on 2024-04-01 UTC** + **Description:** `name` will be removed. + **Reason:** Suggested topics are no longer supported + """ + name: String +} + +""" +Autogenerated return type of AcceptTopicSuggestion. +""" +type AcceptTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The accepted topic. + """ + topic: Topic @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") +} + +""" +Represents an object which can take actions on GitHub. Typically a User or Bot. +""" +interface Actor { + """ + A URL pointing to the actor's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this actor. + """ + resourcePath: URI! + + """ + The HTTP URL for this actor. + """ + url: URI! +} + +""" +Location information for an actor +""" +type ActorLocation { + """ + City + """ + city: String + + """ + Country name + """ + country: String + + """ + Country code + """ + countryCode: String + + """ + Region name + """ + region: String + + """ + Region or state code + """ + regionCode: String +} + +""" +The actor's type. +""" +enum ActorType { + """ + Indicates a user actor. + """ + USER + + """ + Indicates a team actor. + """ + TEAM +} + +""" +Autogenerated input type of AddAssigneesToAssignable +""" +input AddAssigneesToAssignableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the assignable object to add assignees to. + """ + assignableId: ID! + + """ + The id of users to add as assignees. + """ + assigneeIds: [ID!]! +} + +""" +Autogenerated return type of AddAssigneesToAssignable. +""" +type AddAssigneesToAssignablePayload { + """ + The item that was assigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of AddComment +""" +input AddCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + + """ + The contents of the comment. + """ + body: String! +} + +""" +Autogenerated return type of AddComment. +""" +type AddCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the subject's comment connection. + """ + commentEdge: IssueCommentEdge + + """ + The subject + """ + subject: Node + + """ + The edge from the subject's timeline connection. + """ + timelineEdge: IssueTimelineItemEdge +} + +""" +Autogenerated input type of AddDiscussionComment +""" +input AddDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to comment on. + """ + discussionId: ID! + + """ + The Node ID of the discussion comment within this discussion to reply to. + """ + replyToId: ID + + """ + The contents of the comment. + """ + body: String! +} + +""" +Autogenerated return type of AddDiscussionComment. +""" +type AddDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created discussion comment. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of AddDiscussionPollVote +""" +input AddDiscussionPollVoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion poll option to vote for. + """ + pollOptionId: ID! +} + +""" +Autogenerated return type of AddDiscussionPollVote. +""" +type AddDiscussionPollVotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The poll option that a vote was added to. + """ + pollOption: DiscussionPollOption +} + +""" +Autogenerated input type of AddEnterpriseOrganizationMember +""" +input AddEnterpriseOrganizationMemberInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise which owns the organization. + """ + enterpriseId: ID! + + """ + The ID of the organization the users will be added to. + """ + organizationId: ID! + + """ + The IDs of the enterprise members to add. + """ + userIds: [ID!]! + + """ + The role to assign the users in the organization + """ + role: OrganizationMemberRole +} + +""" +Autogenerated return type of AddEnterpriseOrganizationMember. +""" +type AddEnterpriseOrganizationMemberPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The users who were added to the organization. + """ + users: [User!] +} + +""" +Autogenerated input type of AddEnterpriseSupportEntitlement +""" +input AddEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! + + """ + The login of a member who will receive the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of AddEnterpriseSupportEntitlement. +""" +type AddEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of adding the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of AddLabelsToLabelable +""" +input AddLabelsToLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the labelable object to add labels to. + """ + labelableId: ID! + + """ + The ids of the labels to add. + """ + labelIds: [ID!]! +} + +""" +Autogenerated return type of AddLabelsToLabelable. +""" +type AddLabelsToLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was labeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of AddProjectCard +""" +input AddProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the ProjectColumn. + """ + projectColumnId: ID! + + """ + The content of the card. Must be a member of the ProjectCardItem union + """ + contentId: ID + + """ + The note on the card. + """ + note: String +} + +""" +Autogenerated return type of AddProjectCard. +""" +type AddProjectCardPayload { + """ + The edge from the ProjectColumn's card connection. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectColumn + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of AddProjectColumn +""" +input AddProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the project. + """ + projectId: ID! + + """ + The name of the column. + """ + name: String! +} + +""" +Autogenerated return type of AddProjectColumn. +""" +type AddProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The edge from the project's column connection. + """ + columnEdge: ProjectColumnEdge + + """ + The project + """ + project: Project +} + +""" +Autogenerated input type of AddProjectV2DraftIssue +""" +input AddProjectV2DraftIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to add the draft issue to. + """ + projectId: ID! + + """ + The title of the draft issue. A project item can also be created by providing the URL of an Issue or Pull Request if you have access. + """ + title: String! + + """ + The body of the draft issue. + """ + body: String + + """ + The IDs of the assignees of the draft issue. + """ + assigneeIds: [ID!] +} + +""" +Autogenerated return type of AddProjectV2DraftIssue. +""" +type AddProjectV2DraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue added to the project. + """ + projectItem: ProjectV2Item +} + +""" +Autogenerated input type of AddProjectV2ItemById +""" +input AddProjectV2ItemByIdInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to add the item to. + """ + projectId: ID! + + """ + The id of the Issue or Pull Request to add. + """ + contentId: ID! +} + +""" +Autogenerated return type of AddProjectV2ItemById. +""" +type AddProjectV2ItemByIdPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item added to the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of AddPullRequestReviewComment +""" +input AddPullRequestReviewCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the pull request reviewing + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `pullRequestId` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + pullRequestId: ID + + """ + The Node ID of the review to modify. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `pullRequestReviewId` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + pullRequestReviewId: ID + + """ + The SHA of the commit to comment on. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `commitOID` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + commitOID: GitObjectID + + """ + The text of the comment. This field is required + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `body` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + body: String + + """ + The relative path of the file to comment on. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `path` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + path: String + + """ + The line index in the diff to comment on. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `position` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + position: Int + + """ + The comment id to reply to. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `inReplyTo` will be removed. use addPullRequestReviewThread or addPullRequestReviewThreadReply instead + **Reason:** We are deprecating the addPullRequestReviewComment mutation + """ + inReplyTo: ID +} + +""" +Autogenerated return type of AddPullRequestReviewComment. +""" +type AddPullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created comment. + """ + comment: PullRequestReviewComment + + """ + The edge from the review's comment connection. + """ + commentEdge: PullRequestReviewCommentEdge +} + +""" +Autogenerated input type of AddPullRequestReview +""" +input AddPullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! + + """ + The commit OID the review pertains to. + """ + commitOID: GitObjectID + + """ + The contents of the review body comment. + """ + body: String + + """ + The event to perform on the pull request review. + """ + event: PullRequestReviewEvent + + """ + The review line comments. + + **Upcoming Change on 2023-10-01 UTC** + **Description:** `comments` will be removed. use the `threads` argument instead + **Reason:** We are deprecating comment fields that use diff-relative positioning + """ + comments: [DraftPullRequestReviewComment] + + """ + The review line comment threads. + """ + threads: [DraftPullRequestReviewThread] +} + +""" +Autogenerated return type of AddPullRequestReview. +""" +type AddPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created pull request review. + """ + pullRequestReview: PullRequestReview + + """ + The edge from the pull request's review connection. + """ + reviewEdge: PullRequestReviewEdge +} + +""" +Autogenerated input type of AddPullRequestReviewThread +""" +input AddPullRequestReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Path to the file being commented on. + """ + path: String! + + """ + Body of the thread's first comment. + """ + body: String! + + """ + The node ID of the pull request reviewing + """ + pullRequestId: ID + + """ + The Node ID of the review to modify. + """ + pullRequestReviewId: ID + + """ + The line of the blob to which the thread refers, required for line-level threads. The end of the line range for multi-line comments. + """ + line: Int + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT + + """ + The level at which the comments in the corresponding thread are targeted, can be a diff line or a file + """ + subjectType: PullRequestReviewThreadSubjectType = LINE +} + +""" +Autogenerated return type of AddPullRequestReviewThread. +""" +type AddPullRequestReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created thread. + """ + thread: PullRequestReviewThread +} + +""" +Autogenerated input type of AddPullRequestReviewThreadReply +""" +input AddPullRequestReviewThreadReplyInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pending review to which the reply will belong. + """ + pullRequestReviewId: ID + + """ + The Node ID of the thread to which this reply is being written. + """ + pullRequestReviewThreadId: ID! + + """ + The text of the reply. + """ + body: String! +} + +""" +Autogenerated return type of AddPullRequestReviewThreadReply. +""" +type AddPullRequestReviewThreadReplyPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created reply. + """ + comment: PullRequestReviewComment +} + +""" +Autogenerated input type of AddReaction +""" +input AddReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + + """ + The name of the emoji to react with. + """ + content: ReactionContent! +} + +""" +Autogenerated return type of AddReaction. +""" +type AddReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reaction groups for the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of AddStar +""" +input AddStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to star. + """ + starrableId: ID! +} + +""" +Autogenerated return type of AddStar. +""" +type AddStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Autogenerated input type of AddUpvote +""" +input AddUpvoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion or comment to upvote. + """ + subjectId: ID! +} + +""" +Autogenerated return type of AddUpvote. +""" +type AddUpvotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The votable subject. + """ + subject: Votable +} + +""" +Autogenerated input type of AddVerifiableDomain +""" +input AddVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner to add the domain to + """ + ownerId: ID! + + """ + The URL of the domain + """ + domain: URI! +} + +""" +Autogenerated return type of AddVerifiableDomain. +""" +type AddVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was added. + """ + domain: VerifiableDomain +} + +""" +Represents an 'added_to_merge_queue' event on a given pull request. +""" +type AddedToMergeQueueEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who added this Pull Request to the merge queue + """ + enqueuer: User + + """ + The Node ID of the AddedToMergeQueueEvent object + """ + id: ID! + + """ + The merge queue where this pull request was added to. + """ + mergeQueue: MergeQueue + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'added_to_project' event on a given issue or pull request. +""" +type AddedToProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the AddedToProjectEvent object + """ + id: ID! + + """ + Project referenced by event. + """ + project: Project + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard + + """ + Column name referenced by this project event. + """ + projectColumnName: String! +} + +""" +Represents an announcement banner. +""" +interface AnnouncementBanner { + """ + The text of the announcement + """ + announcement: String + + """ + The date the announcement was created + """ + announcementCreatedAt: DateTime + + """ + The expiration date of the announcement, if any + """ + announcementExpiresAt: DateTime + + """ + Whether the announcement can be dismissed by the user + """ + announcementUserDismissible: Boolean +} + +""" +A GitHub App. +""" +type App implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the app. + """ + description: String + + """ + The Node ID of the App object + """ + id: ID! + + """ + The IP addresses of the app. + """ + ipAllowListEntries("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for IP allow list entries returned." orderBy: IpAllowListEntryOrder = { + field: ALLOW_LIST_VALUE + direction: ASC + } + ): IpAllowListEntryConnection! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + A URL pointing to the app's logo. + """ + logoUrl("The size of the resulting image." size: Int): URI! + + """ + The name of the app. + """ + name: String! + + """ + A slug based on the name of the app for use in URLs. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to the app's homepage. + """ + url: URI! +} + +""" +Autogenerated input type of ApproveDeployments +""" +input ApproveDeploymentsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the workflow run containing the pending deployments. + """ + workflowRunId: ID! + + """ + The ids of environments to reject deployments + """ + environmentIds: [ID!]! + + """ + Optional comment for approving deployments + """ + comment: String = "" +} + +""" +Autogenerated return type of ApproveDeployments. +""" +type ApproveDeploymentsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The affected deployments. + """ + deployments: [Deployment!] +} + +""" +Autogenerated input type of ApproveVerifiableDomain +""" +input ApproveVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to approve. + """ + id: ID! +} + +""" +Autogenerated return type of ApproveVerifiableDomain. +""" +type ApproveVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was approved. + """ + domain: VerifiableDomain +} + +""" +Autogenerated input type of ArchiveProjectV2Item +""" +input ArchiveProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to archive the item from. + """ + projectId: ID! + + """ + The ID of the ProjectV2Item to archive. + """ + itemId: ID! +} + +""" +Autogenerated return type of ArchiveProjectV2Item. +""" +type ArchiveProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item archived from the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of ArchiveRepository +""" +input ArchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to mark as archived. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of ArchiveRepository. +""" +type ArchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was marked as archived. + """ + repository: Repository +} + +""" +An object that can have users assigned to it. +""" +interface Assignable { + """ + A list of Users assigned to this object. + """ + assignees("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! +} + +""" +Represents an 'assigned' event on any assignable object. +""" +type AssignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was assigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the AssignedEvent object + """ + id: ID! + + """ + Identifies the user who was assigned. + """ + user: User @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Types that can be assigned to issues. +""" +union Assignee = Bot|Mannequin|Organization|User + +""" +An entry in the audit log. +""" +interface AuditEntry { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types that can initiate an audit log event. +""" +union AuditEntryActor = Bot|Organization|User + +""" +Ordering options for Audit Log connections. +""" +input AuditLogOrder { + """ + The field to order Audit Logs by. + """ + field: AuditLogOrderField + + """ + The ordering direction. + """ + direction: OrderDirection +} + +""" +Properties by which Audit Log connections can be ordered. +""" +enum AuditLogOrderField { + """ + Order audit log entries by timestamp + """ + CREATED_AT +} + +""" +Represents a 'auto_merge_disabled' event on a given pull request. +""" +type AutoMergeDisabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who disabled auto-merge for this Pull Request + """ + disabler: User + + """ + The Node ID of the AutoMergeDisabledEvent object + """ + id: ID! + + """ + PullRequest referenced by event + """ + pullRequest: PullRequest + + """ + The reason auto-merge was disabled + """ + reason: String + + """ + The reason_code relating to why auto-merge was disabled + """ + reasonCode: String +} + +""" +Represents a 'auto_merge_enabled' event on a given pull request. +""" +type AutoMergeEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge for this Pull Request + """ + enabler: User + + """ + The Node ID of the AutoMergeEnabledEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents an auto-merge request for a pull request +""" +type AutoMergeRequest { + """ + The email address of the author of this auto-merge request. + """ + authorEmail: String + + """ + The commit message of the auto-merge request. If a merge queue is required by the base branch, this value will be set by the merge queue when merging. + """ + commitBody: String + + """ + The commit title of the auto-merge request. If a merge queue is required by the base branch, this value will be set by the merge queue when merging + """ + commitHeadline: String + + """ + When was this auto-merge request was enabled. + """ + enabledAt: DateTime + + """ + The actor who created the auto-merge request. + """ + enabledBy: Actor + + """ + The merge method of the auto-merge request. If a merge queue is required by the base branch, this value will be set by the merge queue when merging. + """ + mergeMethod: PullRequestMergeMethod! + + """ + The pull request that this auto-merge request is set against. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'auto_rebase_enabled' event on a given pull request. +""" +type AutoRebaseEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (rebase) for this Pull Request + """ + enabler: User + + """ + The Node ID of the AutoRebaseEnabledEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'auto_squash_enabled' event on a given pull request. +""" +type AutoSquashEnabledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who enabled auto-merge (squash) for this Pull Request + """ + enabler: User + + """ + The Node ID of the AutoSquashEnabledEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'automatic_base_change_failed' event on a given pull request. +""" +type AutomaticBaseChangeFailedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the AutomaticBaseChangeFailedEvent object + """ + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'automatic_base_change_succeeded' event on a given pull request. +""" +type AutomaticBaseChangeSucceededEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the AutomaticBaseChangeSucceededEvent object + """ + id: ID! + + """ + The new base for this PR + """ + newBase: String! + + """ + The old base for this PR + """ + oldBase: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +A (potentially binary) string encoded using base64. +""" +scalar Base64String + +""" +Represents a 'base_ref_changed' event on a given issue or pull request. +""" +type BaseRefChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the name of the base ref for the pull request after it was changed. + """ + currentRefName: String! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the BaseRefChangedEvent object + """ + id: ID! + + """ + Identifies the name of the base ref for the pull request before it was changed. + """ + previousRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'base_ref_deleted' event on a given pull request. +""" +type BaseRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the name of the Ref associated with the `base_ref_deleted` event. + """ + baseRefName: String + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the BaseRefDeletedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest +} + +""" +Represents a 'base_ref_force_pushed' event on a given pull request. +""" +type BaseRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'base_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'base_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the BaseRefForcePushedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'base_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string. +""" +scalar BigInt + +""" +Represents a Git blame. +""" +type Blame { + """ + The list of ranges from a Git blame. + """ + ranges: [BlameRange!]! +} + +""" +Represents a range of information from a Git blame. +""" +type BlameRange { + """ + Identifies the recency of the change, from 1 (new) to 10 (old). This is calculated as a 2-quantile and determines the length of distance between the median age of all the changes in the file and the recency of the current range's change. + """ + age: Int! + + """ + Identifies the line author + """ + commit: Commit! + + """ + The ending line for the range + """ + endingLine: Int! + + """ + The starting line for the range + """ + startingLine: Int! +} + +""" +Represents a Git blob. +""" +type Blob implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + Byte size of Blob object + """ + byteSize: Int! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The Node ID of the Blob object + """ + id: ID! + + """ + Indicates whether the Blob is binary or text. Returns null if unable to determine the encoding. + """ + isBinary: Boolean + + """ + Indicates whether the contents is truncated + """ + isTruncated: Boolean! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + UTF8 text data or null if the Blob is binary + """ + text: String +} + +""" +A special type of user which takes actions on behalf of GitHub Apps. +""" +type Bot implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Bot object + """ + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTTP path for this bot + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this bot + """ + url: URI! +} + +""" +Types which can be actors for `BranchActorAllowance` objects. +""" +union BranchActorAllowanceActor = App|Team|User + +""" +Parameters to be used for the branch_name_pattern rule +""" +type BranchNamePatternParameters { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean! + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Parameters to be used for the branch_name_pattern rule +""" +input BranchNamePatternParametersInput { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +A branch protection rule. +""" +type BranchProtectionRule implements Node { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean! + + """ + A list of conflicts matching branches protection rule and other branch protection rules + """ + branchProtectionRuleConflicts("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): BranchProtectionRuleConflictConnection! + + """ + A list of actors able to force push for this branch protection rule. + """ + bypassForcePushAllowances("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): BypassForcePushAllowanceConnection! + + """ + A list of actors able to bypass PRs for this branch protection rule. + """ + bypassPullRequestAllowances("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): BypassPullRequestAllowanceConnection! + + """ + The actor who created this branch protection rule. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean! + + """ + The Node ID of the BranchProtectionRule object + """ + id: ID! + + """ + Can admins override branch protection. + """ + isAdminEnforced: Boolean! + + """ + Whether users can pull changes from upstream when the branch is locked. Set to `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean! + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean! + + """ + Repository refs that are protected by this rule + """ + matchingRefs("Filters refs with query on name" query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RefConnection! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + A list push allowances for this branch protection rule. + """ + pushAllowances("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PushAllowanceConnection! + + """ + The repository associated with this branch protection rule. + """ + repository: Repository + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean! + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required deployment environments that must be deployed successfully to update matching branches + """ + requiredDeploymentEnvironments: [String] + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + List of required status checks that must pass for commits to be accepted to matching branches. + """ + requiredStatusChecks: [RequiredStatusCheckDescription!] + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean! + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean! + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean! + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean! + + """ + Does this branch require deployment to specific environments before merging + """ + requiresDeployments: Boolean! + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean! + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean! + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean! + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean! + + """ + A list review dismissal allowances for this branch protection rule. + """ + reviewDismissalAllowances("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ReviewDismissalAllowanceConnection! +} + +""" +A conflict between two branch protection rules. +""" +type BranchProtectionRuleConflict { + """ + Identifies the branch protection rule. + """ + branchProtectionRule: BranchProtectionRule + + """ + Identifies the conflicting branch protection rule. + """ + conflictingBranchProtectionRule: BranchProtectionRule + + """ + Identifies the branch ref that has conflicting rules + """ + ref: Ref +} + +""" +The connection type for BranchProtectionRuleConflict. +""" +type BranchProtectionRuleConflictConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleConflictEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRuleConflict] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleConflictEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRuleConflict +} + +""" +The connection type for BranchProtectionRule. +""" +type BranchProtectionRuleConnection { + """ + A list of edges. + """ + edges: [BranchProtectionRuleEdge] + + """ + A list of nodes. + """ + nodes: [BranchProtectionRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BranchProtectionRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BranchProtectionRule +} + +""" +Information about a sponsorship to make for a user or organization with a GitHub Sponsors profile, as part of sponsoring many users or organizations at once. +""" +input BulkSponsorship { + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String + + """ + The amount to pay to the sponsorable in US dollars. Valid values: 1-12000. + """ + amount: Int! +} + +""" +Types that can represent a repository ruleset bypass actor. +""" +union BypassActor = App|Team + +""" +A user, team, or app who has the ability to bypass a force push requirement on a protected branch. +""" +type BypassForcePushAllowance implements Node { + """ + The actor that can force push. + """ + actor: BranchActorAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + + """ + The Node ID of the BypassForcePushAllowance object + """ + id: ID! +} + +""" +The connection type for BypassForcePushAllowance. +""" +type BypassForcePushAllowanceConnection { + """ + A list of edges. + """ + edges: [BypassForcePushAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [BypassForcePushAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BypassForcePushAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BypassForcePushAllowance +} + +""" +A user, team, or app who has the ability to bypass a pull request requirement on a protected branch. +""" +type BypassPullRequestAllowance implements Node { + """ + The actor that can bypass. + """ + actor: BranchActorAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + + """ + The Node ID of the BypassPullRequestAllowance object + """ + id: ID! +} + +""" +The connection type for BypassPullRequestAllowance. +""" +type BypassPullRequestAllowanceConnection { + """ + A list of edges. + """ + edges: [BypassPullRequestAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [BypassPullRequestAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BypassPullRequestAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BypassPullRequestAllowance +} + +""" +The Common Vulnerability Scoring System +""" +type CVSS { + """ + The CVSS score associated with this advisory + """ + score: Float! + + """ + The CVSS vector string associated with this advisory + """ + vectorString: String +} + +""" +A common weakness enumeration +""" +type CWE implements Node { + """ + The id of the CWE + """ + cweId: String! + + """ + A detailed description of this CWE + """ + description: String! + + """ + The Node ID of the CWE object + """ + id: ID! + + """ + The name of this CWE + """ + name: String! +} + +""" +The connection type for CWE. +""" +type CWEConnection { + """ + A list of edges. + """ + edges: [CWEEdge] + + """ + A list of nodes. + """ + nodes: [CWE] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CWEEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CWE +} + +""" +Autogenerated input type of CancelEnterpriseAdminInvitation +""" +input CancelEnterpriseAdminInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pending enterprise administrator invitation. + """ + invitationId: ID! +} + +""" +Autogenerated return type of CancelEnterpriseAdminInvitation. +""" +type CancelEnterpriseAdminInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was canceled. + """ + invitation: EnterpriseAdministratorInvitation + + """ + A message confirming the result of canceling an administrator invitation. + """ + message: String +} + +""" +Autogenerated input type of CancelEnterpriseMemberInvitation +""" +input CancelEnterpriseMemberInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pending enterprise member invitation. + """ + invitationId: ID! +} + +""" +Autogenerated return type of CancelEnterpriseMemberInvitation. +""" +type CancelEnterpriseMemberInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The invitation that was canceled. + """ + invitation: EnterpriseMemberInvitation + + """ + A message confirming the result of canceling an member invitation. + """ + message: String +} + +""" +Autogenerated input type of CancelSponsorship +""" +input CancelSponsorshipInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID + + """ + The username of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String +} + +""" +Autogenerated return type of CancelSponsorship. +""" +type CancelSponsorshipPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The tier that was being used at the time of cancellation. + """ + sponsorsTier: SponsorsTier +} + +""" +Autogenerated input type of ChangeUserStatus +""" +input ChangeUserStatusInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The emoji to represent your status. Can either be a native Unicode emoji or an emoji name with colons, e.g., :grinning:. + """ + emoji: String + + """ + A short description of your current status. + """ + message: String + + """ + The ID of the organization whose members will be allowed to see the status. If omitted, the status will be publicly visible. + """ + organizationId: ID + + """ + Whether this status should indicate you are not fully available on GitHub, e.g., you are away. + """ + limitedAvailability: Boolean = false + + """ + If set, the user status will not be shown after this date. + """ + expiresAt: DateTime +} + +""" +Autogenerated return type of ChangeUserStatus. +""" +type ChangeUserStatusPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Your updated status. + """ + status: UserStatus +} + +""" +A single check annotation. +""" +type CheckAnnotation { + """ + The annotation's severity level. + """ + annotationLevel: CheckAnnotationLevel + + """ + The path to the file that this annotation was made on. + """ + blobUrl: URI! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The position of this annotation. + """ + location: CheckAnnotationSpan! + + """ + The annotation's message. + """ + message: String! + + """ + The path that this annotation was made on. + """ + path: String! + + """ + Additional information about the annotation. + """ + rawDetails: String + + """ + The annotation's title + """ + title: String +} + +""" +The connection type for CheckAnnotation. +""" +type CheckAnnotationConnection { + """ + A list of edges. + """ + edges: [CheckAnnotationEdge] + + """ + A list of nodes. + """ + nodes: [CheckAnnotation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationData { + """ + The path of the file to add an annotation to. + """ + path: String! + + """ + The location of the annotation + """ + location: CheckAnnotationRange! + + """ + Represents an annotation's information level + """ + annotationLevel: CheckAnnotationLevel! + + """ + A short description of the feedback for these lines of code. + """ + message: String! + + """ + The title that represents the annotation. + """ + title: String + + """ + Details about this annotation. + """ + rawDetails: String +} + +""" +An edge in a connection. +""" +type CheckAnnotationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckAnnotation +} + +""" +Represents an annotation's information level. +""" +enum CheckAnnotationLevel { + """ + An annotation indicating an inescapable error. + """ + FAILURE + + """ + An annotation indicating some information. + """ + NOTICE + + """ + An annotation indicating an ignorable error. + """ + WARNING +} + +""" +A character position in a check annotation. +""" +type CheckAnnotationPosition { + """ + Column number (1 indexed). + """ + column: Int + + """ + Line number (1 indexed). + """ + line: Int! +} + +""" +Information from a check run analysis to specific lines of code. +""" +input CheckAnnotationRange { + """ + The starting line of the range. + """ + startLine: Int! + + """ + The starting column of the range. + """ + startColumn: Int + + """ + The ending line of the range. + """ + endLine: Int! + + """ + The ending column of the range. + """ + endColumn: Int +} + +""" +An inclusive pair of positions for a check annotation. +""" +type CheckAnnotationSpan { + """ + End position (inclusive). + """ + end: CheckAnnotationPosition! + + """ + Start position (inclusive). + """ + start: CheckAnnotationPosition! +} + +""" +The possible states for a check suite or run conclusion. +""" +enum CheckConclusionState { + """ + The check suite or run requires action. + """ + ACTION_REQUIRED + + """ + The check suite or run has timed out. + """ + TIMED_OUT + + """ + The check suite or run has been cancelled. + """ + CANCELLED + + """ + The check suite or run has failed. + """ + FAILURE + + """ + The check suite or run has succeeded. + """ + SUCCESS + + """ + The check suite or run was neutral. + """ + NEUTRAL + + """ + The check suite or run was skipped. + """ + SKIPPED + + """ + The check suite or run has failed at startup. + """ + STARTUP_FAILURE + + """ + The check suite or run was marked stale by GitHub. Only GitHub can use this conclusion. + """ + STALE +} + +""" +A check run. +""" +type CheckRun implements Node & RequirableByPullRequest & UniformResourceLocatable { + """ + The check run's annotations + """ + annotations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CheckAnnotationConnection + + """ + The check suite that this run is a part of. + """ + checkSuite: CheckSuite! + + """ + Identifies the date and time when the check run was completed. + """ + completedAt: DateTime + + """ + The conclusion of the check run. + """ + conclusion: CheckConclusionState + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The corresponding deployment for this job, if any + """ + deployment: Deployment + + """ + The URL from which to find full details of the check run on the integrator's site. + """ + detailsUrl: URI + + """ + A reference for the check run on the integrator's system. + """ + externalId: String + + """ + The Node ID of the CheckRun object + """ + id: ID! + + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired("The id of the pull request this is required for" pullRequestId: ID, "The number of the pull request this is required for" pullRequestNumber: Int): Boolean! + + """ + The name of the check for this check run. + """ + name: String! + + """ + Information about a pending deployment, if any, in this check run + """ + pendingDeploymentRequest: DeploymentRequest + + """ + The permalink to the check run summary. + """ + permalink: URI! + + """ + The repository associated with this check run. + """ + repository: Repository! + + """ + The HTTP path for this check run. + """ + resourcePath: URI! + + """ + Identifies the date and time when the check run was started. + """ + startedAt: DateTime + + """ + The current status of the check run. + """ + status: CheckStatusState! + + """ + The check run's steps + """ + steps("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Step number" number: Int): CheckStepConnection + + """ + A string representing the check run's summary + """ + summary: String + + """ + A string representing the check run's text + """ + text: String + + """ + A string representing the check run + """ + title: String + + """ + The HTTP URL for this check run. + """ + url: URI! +} + +""" +Possible further actions the integrator can perform. +""" +input CheckRunAction { + """ + The text to be displayed on a button in the web UI. + """ + label: String! + + """ + A short explanation of what this action would do. + """ + description: String! + + """ + A reference for the action on the integrator's system. + """ + identifier: String! +} + +""" +The connection type for CheckRun. +""" +type CheckRunConnection { + """ + A list of edges. + """ + edges: [CheckRunEdge] + + """ + A list of nodes. + """ + nodes: [CheckRun] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckRunEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckRun +} + +""" +The filters that are available when fetching check runs. +""" +input CheckRunFilter { + """ + Filters the check runs by this type. + """ + checkType: CheckRunType + + """ + Filters the check runs created by this application ID. + """ + appId: Int + + """ + Filters the check runs by this name. + """ + checkName: String + + """ + Filters the check runs by this status. Superceded by statuses. + """ + status: CheckStatusState + + """ + Filters the check runs by this status. Overrides status. + """ + statuses: [CheckStatusState!] + + """ + Filters the check runs by these conclusions. + """ + conclusions: [CheckConclusionState!] +} + +""" +Descriptive details about the check run. +""" +input CheckRunOutput { + """ + A title to provide for this check run. + """ + title: String! + + """ + The summary of the check run (supports Commonmark). + """ + summary: String! + + """ + The details of the check run (supports Commonmark). + """ + text: String + + """ + The annotations that are made as part of the check run. + """ + annotations: [CheckAnnotationData!] + + """ + Images attached to the check run output displayed in the GitHub pull request UI. + """ + images: [CheckRunOutputImage!] +} + +""" +Images attached to the check run output displayed in the GitHub pull request UI. +""" +input CheckRunOutputImage { + """ + The alternative text for the image. + """ + alt: String! + + """ + The full URL of the image. + """ + imageUrl: URI! + + """ + A short image description. + """ + caption: String +} + +""" +The possible states of a check run in a status rollup. +""" +enum CheckRunState { + """ + The check run requires action. + """ + ACTION_REQUIRED + + """ + The check run has been cancelled. + """ + CANCELLED + + """ + The check run has been completed. + """ + COMPLETED + + """ + The check run has failed. + """ + FAILURE + + """ + The check run is in progress. + """ + IN_PROGRESS + + """ + The check run was neutral. + """ + NEUTRAL + + """ + The check run is in pending state. + """ + PENDING + + """ + The check run has been queued. + """ + QUEUED + + """ + The check run was skipped. + """ + SKIPPED + + """ + The check run was marked stale by GitHub. Only GitHub can use this conclusion. + """ + STALE + + """ + The check run has failed at startup. + """ + STARTUP_FAILURE + + """ + The check run has succeeded. + """ + SUCCESS + + """ + The check run has timed out. + """ + TIMED_OUT + + """ + The check run is in waiting state. + """ + WAITING +} + +""" +Represents a count of the state of a check run. +""" +type CheckRunStateCount { + """ + The number of check runs with this state. + """ + count: Int! + + """ + The state of a check run. + """ + state: CheckRunState! +} + +""" +The possible types of check runs. +""" +enum CheckRunType { + """ + Every check run available. + """ + ALL + + """ + The latest check run. + """ + LATEST +} + +""" +The possible states for a check suite or run status. +""" +enum CheckStatusState { + """ + The check suite or run has been requested. + """ + REQUESTED + + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in waiting state. + """ + WAITING + + """ + The check suite or run is in pending state. + """ + PENDING +} + +""" +A single check step. +""" +type CheckStep { + """ + Identifies the date and time when the check step was completed. + """ + completedAt: DateTime + + """ + The conclusion of the check step. + """ + conclusion: CheckConclusionState + + """ + A reference for the check step on the integrator's system. + """ + externalId: String + + """ + The step's name. + """ + name: String! + + """ + The index of the step in the list of steps of the parent check run. + """ + number: Int! + + """ + Number of seconds to completion. + """ + secondsToCompletion: Int + + """ + Identifies the date and time when the check step was started. + """ + startedAt: DateTime + + """ + The current status of the check step. + """ + status: CheckStatusState! +} + +""" +The connection type for CheckStep. +""" +type CheckStepConnection { + """ + A list of edges. + """ + edges: [CheckStepEdge] + + """ + A list of nodes. + """ + nodes: [CheckStep] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckStepEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckStep +} + +""" +A check suite. +""" +type CheckSuite implements Node { + """ + The GitHub App which created this check suite. + """ + app: App + + """ + The name of the branch for this check suite. + """ + branch: Ref + + """ + The check runs associated with a check suite. + """ + checkRuns("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filters the check runs by this type." filterBy: CheckRunFilter): CheckRunConnection + + """ + The commit for this check suite + """ + commit: Commit! + + """ + The conclusion of this check suite. + """ + conclusion: CheckConclusionState + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who triggered the check suite. + """ + creator: User + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the CheckSuite object + """ + id: ID! + + """ + A list of open pull requests matching the check suite. + """ + matchingPullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection + + """ + The push that triggered this check suite. + """ + push: Push + + """ + The repository associated with this check suite. + """ + repository: Repository! + + """ + The HTTP path for this check suite + """ + resourcePath: URI! + + """ + The status of this check suite. + """ + status: CheckStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this check suite + """ + url: URI! + + """ + The workflow run associated with this check suite. + """ + workflowRun: WorkflowRun +} + +""" +The auto-trigger preferences that are available for check suites. +""" +input CheckSuiteAutoTriggerPreference { + """ + The node ID of the application that owns the check suite. + """ + appId: ID! + + """ + Set to `true` to enable automatic creation of CheckSuite events upon pushes to the repository. + """ + setting: Boolean! +} + +""" +The connection type for CheckSuite. +""" +type CheckSuiteConnection { + """ + A list of edges. + """ + edges: [CheckSuiteEdge] + + """ + A list of nodes. + """ + nodes: [CheckSuite] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CheckSuiteEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CheckSuite +} + +""" +The filters that are available when fetching check suites. +""" +input CheckSuiteFilter { + """ + Filters the check suites created by this application ID. + """ + appId: Int + + """ + Filters the check suites by this name. + """ + checkName: String +} + +""" +An object which can have its data claimed or claim data from another. +""" +union Claimable = Mannequin|User + +""" +Autogenerated input type of ClearLabelsFromLabelable +""" +input ClearLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the labelable object to clear the labels from. + """ + labelableId: ID! +} + +""" +Autogenerated return type of ClearLabelsFromLabelable. +""" +type ClearLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlabeled. + """ + labelable: Labelable +} + +""" +Autogenerated input type of ClearProjectV2ItemFieldValue +""" +input ClearProjectV2ItemFieldValueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project. + """ + projectId: ID! + + """ + The ID of the item to be cleared. + """ + itemId: ID! + + """ + The ID of the field to be cleared. + """ + fieldId: ID! +} + +""" +Autogenerated return type of ClearProjectV2ItemFieldValue. +""" +type ClearProjectV2ItemFieldValuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated item. + """ + projectV2Item: ProjectV2Item +} + +""" +Autogenerated input type of CloneProject +""" +input CloneProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner ID to create the project under. + """ + targetOwnerId: ID! + + """ + The source project to clone. + """ + sourceId: ID! + + """ + Whether or not to clone the source project's workflows. + """ + includeWorkflows: Boolean! + + """ + The name of the project. + """ + name: String! + + """ + The description of the project. + """ + body: String + + """ + The visibility of the project, defaults to false (private). + """ + public: Boolean +} + +""" +Autogenerated return type of CloneProject. +""" +type CloneProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the JobStatus for populating cloned fields. + """ + jobStatusId: String + + """ + The new cloned project. + """ + project: Project +} + +""" +Autogenerated input type of CloneTemplateRepository +""" +input CloneTemplateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the template repository. + """ + repositoryId: ID! + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID! + + """ + A short description of the new repository. + """ + description: String + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! + + """ + Whether to copy all branches from the template to the new repository. Defaults to copying only the default branch of the template. + """ + includeAllBranches: Boolean = false +} + +""" +Autogenerated return type of CloneTemplateRepository. +""" +type CloneTemplateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +An object that can be closed +""" +interface Closable { + """ + Indicates if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! +} + +""" +Autogenerated input type of CloseDiscussion +""" +input CloseDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the discussion to be closed. + """ + discussionId: ID! + + """ + The reason why the discussion is being closed. + """ + reason: DiscussionCloseReason = RESOLVED +} + +""" +Autogenerated return type of CloseDiscussion. +""" +type CloseDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was closed. + """ + discussion: Discussion +} + +""" +Autogenerated input type of CloseIssue +""" +input CloseIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be closed. + """ + issueId: ID! + + """ + The reason the issue is to be closed. + """ + stateReason: IssueClosedStateReason +} + +""" +Autogenerated return type of CloseIssue. +""" +type CloseIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was closed. + """ + issue: Issue +} + +""" +Autogenerated input type of ClosePullRequest +""" +input ClosePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be closed. + """ + pullRequestId: ID! +} + +""" +Autogenerated return type of ClosePullRequest. +""" +type ClosePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was closed. + """ + pullRequest: PullRequest +} + +""" +Represents a 'closed' event on any `Closable`. +""" +type ClosedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was closed. + """ + closable: Closable! + + """ + Object which triggered the creation of this event. + """ + closer: Closer + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ClosedEvent object + """ + id: ID! + + """ + The HTTP path for this closed event. + """ + resourcePath: URI! + + """ + The reason the issue state was changed to closed. + """ + stateReason: IssueStateReason + + """ + The HTTP URL for this closed event. + """ + url: URI! +} + +""" +The object which triggered a `ClosedEvent`. +""" +union Closer = Commit|ProjectV2|PullRequest + +""" +The Code of Conduct for a repository +""" +type CodeOfConduct implements Node { + """ + The body of the Code of Conduct + """ + body: String + + """ + The Node ID of the CodeOfConduct object + """ + id: ID! + + """ + The key for the Code of Conduct + """ + key: String! + + """ + The formal name of the Code of Conduct + """ + name: String! + + """ + The HTTP path for this Code of Conduct + """ + resourcePath: URI + + """ + The HTTP URL for this Code of Conduct + """ + url: URI +} + +""" +Choose which tools must provide code scanning results before the reference is updated. When configured, code scanning must be enabled and have results for both the commit and the reference being updated. +""" +type CodeScanningParameters { + """ + Tools that must provide code scanning results for this rule to pass. + """ + codeScanningTools: [CodeScanningTool!]! +} + +""" +Choose which tools must provide code scanning results before the reference is updated. When configured, code scanning must be enabled and have results for both the commit and the reference being updated. +""" +input CodeScanningParametersInput { + """ + Tools that must provide code scanning results for this rule to pass. + """ + codeScanningTools: [CodeScanningToolInput!]! +} + +""" +A tool that must provide code scanning results for this rule to pass. +""" +type CodeScanningTool { + """ + The severity level at which code scanning results that raise alerts block a reference update. For more information on alert severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + """ + alertsThreshold: String! + + """ + The severity level at which code scanning results that raise security alerts block a reference update. For more information on security severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + """ + securityAlertsThreshold: String! + + """ + The name of a code scanning tool + """ + tool: String! +} + +""" +A tool that must provide code scanning results for this rule to pass. +""" +input CodeScanningToolInput { + """ + The severity level at which code scanning results that raise alerts block a reference update. For more information on alert severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + """ + alertsThreshold: String! + + """ + The severity level at which code scanning results that raise security alerts block a reference update. For more information on security severity levels, see "[About code scanning alerts](${externalDocsUrl}/code-security/code-scanning/managing-code-scanning-alerts/about-code-scanning-alerts#about-alert-severity-and-security-severity-levels)." + """ + securityAlertsThreshold: String! + + """ + The name of a code scanning tool + """ + tool: String! +} + +""" +Collaborators affiliation level with a subject. +""" +enum CollaboratorAffiliation { + """ + All outside collaborators of an organization-owned subject. + """ + OUTSIDE + + """ + All collaborators with permissions to an organization-owned subject, regardless of organization membership status. + """ + DIRECT + + """ + All collaborators the authenticated user can see. + """ + ALL +} + +""" +Represents a comment. +""" +interface Comment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the Comment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A comment author association with repository. +""" +enum CommentAuthorAssociation { + """ + Author is a member of the organization that owns the repository. + """ + MEMBER + + """ + Author is the owner of the repository. + """ + OWNER + + """ + Author is a placeholder for an unclaimed user. + """ + MANNEQUIN + + """ + Author has been invited to collaborate on the repository. + """ + COLLABORATOR + + """ + Author has previously committed to the repository. + """ + CONTRIBUTOR + + """ + Author has not previously committed to the repository. + """ + FIRST_TIME_CONTRIBUTOR + + """ + Author has not previously committed to GitHub. + """ + FIRST_TIMER + + """ + Author has no association with the repository. + """ + NONE +} + +""" +The possible errors that will prevent a user from updating a comment. +""" +enum CommentCannotUpdateReason { + """ + Unable to create comment because repository is archived. + """ + ARCHIVED + + """ + You must be the author or have write access to this repository to update this comment. + """ + INSUFFICIENT_ACCESS + + """ + Unable to create comment because issue is locked. + """ + LOCKED + + """ + You must be logged in to update this comment. + """ + LOGIN_REQUIRED + + """ + Repository is under maintenance. + """ + MAINTENANCE + + """ + At least one email address must be verified to update this comment. + """ + VERIFIED_EMAIL_REQUIRED + + """ + You cannot update this comment + """ + DENIED +} + +""" +Represents a 'comment_deleted' event on a given issue or pull request. +""" +type CommentDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user who authored the deleted comment. + """ + deletedCommentAuthor: Actor + + """ + The Node ID of the CommentDeletedEvent object + """ + id: ID! +} + +""" +Represents a Git commit. +""" +type Commit implements GitObject & Node & Subscribable & UniformResourceLocatable { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The number of additions in this commit. + """ + additions: Int! + + """ + The merged Pull Request that introduced the commit to the repository. If the commit is not present in the default branch, additionally returns open Pull Requests associated with the commit + """ + associatedPullRequests("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for pull requests." orderBy: PullRequestOrder = { + field: CREATED_AT + direction: ASC + } + ): PullRequestConnection + + """ + Authorship details of the commit. + """ + author: GitActor + + """ + Check if the committer and the author match. + """ + authoredByCommitter: Boolean! + + """ + The datetime when this commit was authored. + """ + authoredDate: DateTime! + + """ + The list of authors for this commit based on the git author and the Co-authored-by + message trailer. The git author will always be first. + """ + authors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): GitActorConnection! + + """ + Fetches `git blame` information. + """ + blame("The file whose Git blame information you want." path: String!): Blame! + + """ + We recommend using the `changedFilesIfAvailable` field instead of `changedFiles`, as `changedFiles` will cause your request to return an error if GitHub is unable to calculate the number of changed files. + """ + changedFiles: Int! @deprecated(reason: "`changedFiles` will be removed. Use `changedFilesIfAvailable` instead. Removal on 2023-01-01 UTC.") + + """ + The number of changed files in this commit. If GitHub is unable to calculate the number of changed files (for example due to a timeout), this will return `null`. We recommend using this field instead of `changedFiles`. + """ + changedFilesIfAvailable: Int + + """ + The check suites associated with a commit. + """ + checkSuites("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filters the check suites by this type." filterBy: CheckSuiteFilter): CheckSuiteConnection + + """ + Comments made on the commit. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitCommentConnection! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The datetime when this commit was committed. + """ + committedDate: DateTime! + + """ + Check if committed via GitHub web UI. + """ + committedViaWeb: Boolean! + + """ + Committer details of the commit. + """ + committer: GitActor + + """ + The number of deletions in this commit. + """ + deletions: Int! + + """ + The deployments associated with a commit. + """ + deployments("Environments to list deployments for" environments: [String!], "Ordering options for deployments returned from the connection." orderBy: DeploymentOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentConnection + + """ + The tree entry representing the file located at the given path. + """ + file("The path for the file" path: String!): TreeEntry + + """ + The linear commit history starting from (and including) this commit, in the same order as `git log`. + """ + history("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If non-null, filters history to only show commits touching files under this path." path: String, "If non-null, filters history to only show commits with matching authorship." author: CommitAuthor, "Allows specifying a beginning time or date for fetching commits. Unexpected results may be returned for dates not between 1970-01-01 and 2099-12-13 (inclusive)." since: GitTimestamp, "Allows specifying an ending time or date for fetching commits. Unexpected results may be returned for dates not between 1970-01-01 and 2099-12-13 (inclusive)." until: GitTimestamp): CommitHistoryConnection! + + """ + The Node ID of the Commit object + """ + id: ID! + + """ + The Git commit message + """ + message: String! + + """ + The Git commit message body + """ + messageBody: String! + + """ + The commit message body rendered to HTML. + """ + messageBodyHTML: HTML! + + """ + The Git commit message headline + """ + messageHeadline: String! + + """ + The commit message headline rendered to HTML. + """ + messageHeadlineHTML: HTML! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The organization this commit was made on behalf of. + """ + onBehalfOf: Organization + + """ + The parents of a commit. + """ + parents("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitConnection! + + """ + The datetime when this commit was pushed. + """ + pushedDate: DateTime @deprecated(reason: "`pushedDate` is no longer supported. Removal on 2023-07-01 UTC.") + + """ + The Repository this commit belongs to + """ + repository: Repository! + + """ + The HTTP path for this commit + """ + resourcePath: URI! + + """ + Commit signing information, if present. + """ + signature: GitSignature + + """ + Status information for this commit + """ + status: Status + + """ + Check and Status rollup information for this commit. + """ + statusCheckRollup: StatusCheckRollup + + """ + Returns a list of all submodules in this repository as of this Commit parsed from the .gitmodules file. + """ + submodules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SubmoduleConnection! + + """ + Returns a URL to download a tarball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + tarballUrl: URI! + + """ + Commit's root Tree + """ + tree: Tree! + + """ + The HTTP path for the tree of this commit + """ + treeResourcePath: URI! + + """ + The HTTP URL for the tree of this commit + """ + treeUrl: URI! + + """ + The HTTP URL for this commit + """ + url: URI! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Returns a URL to download a zipball archive for a repository. + Note: For private repositories, these links are temporary and expire after five minutes. + """ + zipballUrl: URI! +} + +""" +Specifies an author for filtering Git commits. +""" +input CommitAuthor { + """ + ID of a User to filter by. If non-null, only commits authored by this user will be returned. This field takes precedence over emails. + """ + id: ID + + """ + Email addresses to filter by. Commits authored by any of the specified email addresses will be returned. + """ + emails: [String!] +} + +""" +Parameters to be used for the commit_author_email_pattern rule +""" +type CommitAuthorEmailPatternParameters { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean! + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Parameters to be used for the commit_author_email_pattern rule +""" +input CommitAuthorEmailPatternParametersInput { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Represents a comment on a given Commit. +""" +type CommitComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment, if the commit exists. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the CommitComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies the file path associated with the comment. + """ + path: String + + """ + Identifies the line position associated with the comment. + """ + position: Int + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this commit comment. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this commit comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for CommitComment. +""" +type CommitCommentConnection { + """ + A list of edges. + """ + edges: [CommitCommentEdge] + + """ + A list of nodes. + """ + nodes: [CommitComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CommitCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CommitComment +} + +""" +A thread of comments on a commit. +""" +type CommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit + + """ + The Node ID of the CommitCommentThread object + """ + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for Commit. +""" +type CommitConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Ordering options for commit contribution connections. +""" +input CommitContributionOrder { + """ + The field by which to order commit contributions. + """ + field: CommitContributionOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which commit contribution connections can be ordered. +""" +enum CommitContributionOrderField { + """ + Order commit contributions by when they were made. + """ + OCCURRED_AT + + """ + Order commit contributions by how many commits they represent. + """ + COMMIT_COUNT +} + +""" +This aggregates commits made by a user within one repository. +""" +type CommitContributionsByRepository { + """ + The commit contributions, each representing a day. + """ + contributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for commit contributions returned from the connection." orderBy: CommitContributionOrder = { + field: OCCURRED_AT + direction: DESC + } + ): CreatedCommitContributionConnection! + + """ + The repository in which the commits were made. + """ + repository: Repository! + + """ + The HTTP path for the user's commits to the repository in this time range. + """ + resourcePath: URI! + + """ + The HTTP URL for the user's commits to the repository in this time range. + """ + url: URI! +} + +""" +An edge in a connection. +""" +type CommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Commit +} + +""" +The connection type for Commit. +""" +type CommitHistoryConnection { + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A message to include with a new commit +""" +input CommitMessage { + """ + The headline of the message. + """ + headline: String! + + """ + The body of the message. + """ + body: String +} + +""" +Parameters to be used for the commit_message_pattern rule +""" +type CommitMessagePatternParameters { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean! + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Parameters to be used for the commit_message_pattern rule +""" +input CommitMessagePatternParametersInput { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +A git ref for a commit to be appended to. + +The ref must be a branch, i.e. its fully qualified name must start +with `refs/heads/` (although the input is not required to be fully +qualified). + +The Ref may be specified by its global node ID or by the +`repositoryNameWithOwner` and `branchName`. + +### Examples + +Specify a branch using a global node ID: + + { "id": "MDM6UmVmMTpyZWZzL2hlYWRzL21haW4=" } + +Specify a branch using `repositoryNameWithOwner` and `branchName`: + + { + "repositoryNameWithOwner": "github/graphql-client", + "branchName": "main" + } + +""" +input CommittableBranch { + """ + The Node ID of the Ref to be updated. + """ + id: ID + + """ + The nameWithOwner of the repository to commit to. + """ + repositoryNameWithOwner: String + + """ + The unqualified name of the branch to append the commit to. + """ + branchName: String +} + +""" +Parameters to be used for the committer_email_pattern rule +""" +type CommitterEmailPatternParameters { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean! + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Parameters to be used for the committer_email_pattern rule +""" +input CommitterEmailPatternParametersInput { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Represents a comparison between two commit revisions. +""" +type Comparison implements Node { + """ + The number of commits ahead of the base branch. + """ + aheadBy: Int! + + """ + The base revision of this comparison. + """ + baseTarget: GitObject! + + """ + The number of commits behind the base branch. + """ + behindBy: Int! + + """ + The commits which compose this comparison. + """ + commits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ComparisonCommitConnection! + + """ + The head revision of this comparison. + """ + headTarget: GitObject! + + """ + The Node ID of the Comparison object + """ + id: ID! + + """ + The status of this comparison. + """ + status: ComparisonStatus! +} + +""" +The connection type for Commit. +""" +type ComparisonCommitConnection { + """ + The total count of authors and co-authors across all commits. + """ + authorCount: Int! + + """ + A list of edges. + """ + edges: [CommitEdge] + + """ + A list of nodes. + """ + nodes: [Commit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The status of a git comparison between two refs. +""" +enum ComparisonStatus { + """ + The head ref is both ahead and behind of the base ref, indicating git history has diverged. + """ + DIVERGED + + """ + The head ref is ahead of the base ref. + """ + AHEAD + + """ + The head ref is behind the base ref. + """ + BEHIND + + """ + The head ref and base ref are identical. + """ + IDENTICAL +} + +""" +Represents a 'connected' event on a given issue or pull request. +""" +type ConnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ConnectedEvent object + """ + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was connected. + """ + subject: ReferencedSubject! +} + +""" +The Contributing Guidelines for a repository. +""" +type ContributingGuidelines { + """ + The body of the Contributing Guidelines. + """ + body: String + + """ + The HTTP path for the Contributing Guidelines. + """ + resourcePath: URI + + """ + The HTTP URL for the Contributing Guidelines. + """ + url: URI +} + +""" +Represents a contribution a user made on GitHub, such as opening an issue. +""" +interface Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A calendar of contributions made on GitHub by a user. +""" +type ContributionCalendar { + """ + A list of hex color codes used in this calendar. The darker the color, the more contributions it represents. + """ + colors: [String!]! + + """ + Determine if the color set was chosen because it's currently Halloween. + """ + isHalloween: Boolean! + + """ + A list of the months of contributions in this calendar. + """ + months: [ContributionCalendarMonth!]! + + """ + The count of total contributions in the calendar. + """ + totalContributions: Int! + + """ + A list of the weeks of contributions in this calendar. + """ + weeks: [ContributionCalendarWeek!]! +} + +""" +Represents a single day of contributions on GitHub by a user. +""" +type ContributionCalendarDay { + """ + The hex color code that represents how many contributions were made on this day compared to others in the calendar. + """ + color: String! + + """ + How many contributions were made by the user on this day. + """ + contributionCount: Int! + + """ + Indication of contributions, relative to other days. Can be used to indicate which color to represent this day on a calendar. + """ + contributionLevel: ContributionLevel! + + """ + The day this square represents. + """ + date: Date! + + """ + A number representing which day of the week this square represents, e.g., 1 is Monday. + """ + weekday: Int! +} + +""" +A month of contributions in a user's contribution graph. +""" +type ContributionCalendarMonth { + """ + The date of the first day of this month. + """ + firstDay: Date! + + """ + The name of the month. + """ + name: String! + + """ + How many weeks started in this month. + """ + totalWeeks: Int! + + """ + The year the month occurred in. + """ + year: Int! +} + +""" +A week of contributions in a user's contribution graph. +""" +type ContributionCalendarWeek { + """ + The days of contributions in this week. + """ + contributionDays: [ContributionCalendarDay!]! + + """ + The date of the earliest square in this week. + """ + firstDay: Date! +} + +""" +Varying levels of contributions from none to many. +""" +enum ContributionLevel { + """ + No contributions occurred. + """ + NONE + + """ + Lowest 25% of days of contributions. + """ + FIRST_QUARTILE + + """ + Second lowest 25% of days of contributions. More contributions than the first quartile. + """ + SECOND_QUARTILE + + """ + Second highest 25% of days of contributions. More contributions than second quartile, less than the fourth quartile. + """ + THIRD_QUARTILE + + """ + Highest 25% of days of contributions. More contributions than the third quartile. + """ + FOURTH_QUARTILE +} + +""" +Ordering options for contribution connections. +""" +input ContributionOrder { + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +A contributions collection aggregates contributions such as opened issues and commits created by a user. +""" +type ContributionsCollection { + """ + Commit contributions made by the user, grouped by repository. + """ + commitContributionsByRepository("How many repositories should be included." maxRepositories: Int = 25): [CommitContributionsByRepository!]! + + """ + A calendar of this user's contributions on GitHub. + """ + contributionCalendar: ContributionCalendar! + + """ + The years the user has been making contributions with the most recent year first. + """ + contributionYears: [Int!]! + + """ + Determine if this collection's time span ends in the current month. + """ + doesEndInCurrentMonth: Boolean! + + """ + The date of the first restricted contribution the user made in this time period. Can only be non-null when the user has enabled private contribution counts. + """ + earliestRestrictedContributionDate: Date + + """ + The ending date and time of this collection. + """ + endedAt: DateTime! + + """ + The first issue the user opened on GitHub. This will be null if that issue was opened outside the collection's time range and ignoreTimeRange is false. If the issue is not visible but the user has opted to show private contributions, a RestrictedContribution will be returned. + """ + firstIssueContribution: CreatedIssueOrRestrictedContribution + + """ + The first pull request the user opened on GitHub. This will be null if that pull request was opened outside the collection's time range and ignoreTimeRange is not true. If the pull request is not visible but the user has opted to show private contributions, a RestrictedContribution will be returned. + """ + firstPullRequestContribution: CreatedPullRequestOrRestrictedContribution + + """ + The first repository the user created on GitHub. This will be null if that first repository was created outside the collection's time range and ignoreTimeRange is false. If the repository is not visible, then a RestrictedContribution is returned. + """ + firstRepositoryContribution: CreatedRepositoryOrRestrictedContribution + + """ + Does the user have any more activity in the timeline that occurred prior to the collection's time range? + """ + hasActivityInThePast: Boolean! + + """ + Determine if there are any contributions in this collection. + """ + hasAnyContributions: Boolean! + + """ + Determine if the user made any contributions in this time frame whose details are not visible because they were made in a private repository. Can only be true if the user enabled private contribution counts. + """ + hasAnyRestrictedContributions: Boolean! + + """ + Whether or not the collector's time span is all within the same day. + """ + isSingleDay: Boolean! + + """ + A list of issues the user opened. + """ + issueContributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Should the user's first issue ever be excluded from the result." excludeFirst: Boolean = false, "Should the user's most commented issue be excluded from the result." excludePopular: Boolean = false, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedIssueContributionConnection! + + """ + Issue contributions made by the user, grouped by repository. + """ + issueContributionsByRepository("How many repositories should be included." maxRepositories: Int = 25, "Should the user's first issue ever be excluded from the result." excludeFirst: Boolean = false, "Should the user's most commented issue be excluded from the result." excludePopular: Boolean = false): [IssueContributionsByRepository!]! + + """ + When the user signed up for GitHub. This will be null if that sign up date falls outside the collection's time range and ignoreTimeRange is false. + """ + joinedGitHubContribution: JoinedGitHubContribution + + """ + The date of the most recent restricted contribution the user made in this time period. Can only be non-null when the user has enabled private contribution counts. + """ + latestRestrictedContributionDate: Date + + """ + When this collection's time range does not include any activity from the user, use this + to get a different collection from an earlier time range that does have activity. + """ + mostRecentCollectionWithActivity: ContributionsCollection + + """ + Returns a different contributions collection from an earlier time range than this one + that does not have any contributions. + """ + mostRecentCollectionWithoutActivity: ContributionsCollection + + """ + The issue the user opened on GitHub that received the most comments in the specified + time frame. + """ + popularIssueContribution: CreatedIssueContribution + + """ + The pull request the user opened on GitHub that received the most comments in the + specified time frame. + """ + popularPullRequestContribution: CreatedPullRequestContribution + + """ + Pull request contributions made by the user. + """ + pullRequestContributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Should the user's first pull request ever be excluded from the result." excludeFirst: Boolean = false, "Should the user's most commented pull request be excluded from the result." excludePopular: Boolean = false, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedPullRequestContributionConnection! + + """ + Pull request contributions made by the user, grouped by repository. + """ + pullRequestContributionsByRepository("How many repositories should be included." maxRepositories: Int = 25, "Should the user's first pull request ever be excluded from the result." excludeFirst: Boolean = false, "Should the user's most commented pull request be excluded from the result." excludePopular: Boolean = false): [PullRequestContributionsByRepository!]! + + """ + Pull request review contributions made by the user. Returns the most recently + submitted review for each PR reviewed by the user. + """ + pullRequestReviewContributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedPullRequestReviewContributionConnection! + + """ + Pull request review contributions made by the user, grouped by repository. + """ + pullRequestReviewContributionsByRepository("How many repositories should be included." maxRepositories: Int = 25): [PullRequestReviewContributionsByRepository!]! + + """ + A list of repositories owned by the user that the user created in this time range. + """ + repositoryContributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Should the user's first repository ever be excluded from the result." excludeFirst: Boolean = false, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedRepositoryContributionConnection! + + """ + A count of contributions made by the user that the viewer cannot access. Only non-zero when the user has chosen to share their private contribution counts. + """ + restrictedContributionsCount: Int! + + """ + The beginning date and time of this collection. + """ + startedAt: DateTime! + + """ + How many commits were made by the user in this time span. + """ + totalCommitContributions: Int! + + """ + How many issues the user opened. + """ + totalIssueContributions("Should the user's first issue ever be excluded from this count." excludeFirst: Boolean = false, "Should the user's most commented issue be excluded from this count." excludePopular: Boolean = false): Int! + + """ + How many pull requests the user opened. + """ + totalPullRequestContributions("Should the user's first pull request ever be excluded from this count." excludeFirst: Boolean = false, "Should the user's most commented pull request be excluded from this count." excludePopular: Boolean = false): Int! + + """ + How many pull request reviews the user left. + """ + totalPullRequestReviewContributions: Int! + + """ + How many different repositories the user committed to. + """ + totalRepositoriesWithContributedCommits: Int! + + """ + How many different repositories the user opened issues in. + """ + totalRepositoriesWithContributedIssues("Should the user's first issue ever be excluded from this count." excludeFirst: Boolean = false, "Should the user's most commented issue be excluded from this count." excludePopular: Boolean = false): Int! + + """ + How many different repositories the user left pull request reviews in. + """ + totalRepositoriesWithContributedPullRequestReviews: Int! + + """ + How many different repositories the user opened pull requests in. + """ + totalRepositoriesWithContributedPullRequests("Should the user's first pull request ever be excluded from this count." excludeFirst: Boolean = false, "Should the user's most commented pull request be excluded from this count." excludePopular: Boolean = false): Int! + + """ + How many repositories the user created. + """ + totalRepositoryContributions("Should the user's first repository ever be excluded from this count." excludeFirst: Boolean = false): Int! + + """ + The user who made the contributions in this collection. + """ + user: User! +} + +""" +Autogenerated input type of ConvertProjectCardNoteToIssue +""" +input ConvertProjectCardNoteToIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectCard ID to convert. + """ + projectCardId: ID! + + """ + The ID of the repository to create the issue in. + """ + repositoryId: ID! + + """ + The title of the newly created issue. Defaults to the card's note text. + """ + title: String + + """ + The body of the newly created issue. + """ + body: String +} + +""" +Autogenerated return type of ConvertProjectCardNoteToIssue. +""" +type ConvertProjectCardNoteToIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Autogenerated input type of ConvertProjectV2DraftIssueItemToIssue +""" +input ConvertProjectV2DraftIssueItemToIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the draft issue ProjectV2Item to convert. + """ + itemId: ID! + + """ + The ID of the repository to create the issue in. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of ConvertProjectV2DraftIssueItemToIssue. +""" +type ConvertProjectV2DraftIssueItemToIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project item. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of ConvertPullRequestToDraft +""" +input ConvertPullRequestToDraftInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to convert to draft + """ + pullRequestId: ID! +} + +""" +Autogenerated return type of ConvertPullRequestToDraft. +""" +type ConvertPullRequestToDraftPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is now a draft. + """ + pullRequest: PullRequest +} + +""" +Represents a 'convert_to_draft' event on a given pull request. +""" +type ConvertToDraftEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ConvertToDraftEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this convert to draft event. + """ + resourcePath: URI! + + """ + The HTTP URL for this convert to draft event. + """ + url: URI! +} + +""" +Represents a 'converted_note_to_issue' event on a given issue or pull request. +""" +type ConvertedNoteToIssueEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ConvertedNoteToIssueEvent object + """ + id: ID! + + """ + Project referenced by event. + """ + project: Project + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard + + """ + Column name referenced by this project event. + """ + projectColumnName: String! +} + +""" +Represents a 'converted_to_discussion' event on a given issue. +""" +type ConvertedToDiscussionEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The discussion that the issue was converted into. + """ + discussion: Discussion + + """ + The Node ID of the ConvertedToDiscussionEvent object + """ + id: ID! +} + +""" +Copilot endpoint information +""" +type CopilotEndpoints { + """ + Copilot API endpoint + """ + api: String! + + """ + Copilot origin tracker endpoint + """ + originTracker: String! + + """ + Copilot proxy endpoint + """ + proxy: String! + + """ + Copilot telemetry endpoint + """ + telemetry: String! +} + +""" +Autogenerated input type of CopyProjectV2 +""" +input CopyProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the source Project to copy. + """ + projectId: ID! + + """ + The owner ID of the new project. + """ + ownerId: ID! + + """ + The title of the project. + """ + title: String! + + """ + Include draft issues in the new project + """ + includeDraftIssues: Boolean = false +} + +""" +Autogenerated return type of CopyProjectV2. +""" +type CopyProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The copied project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of CreateAttributionInvitation +""" +input CreateAttributionInvitationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the owner scoping the reattributable data. + """ + ownerId: ID! + + """ + The Node ID of the account owning the data to reattribute. + """ + sourceId: ID! + + """ + The Node ID of the account which may claim the data. + """ + targetId: ID! +} + +""" +Autogenerated return type of CreateAttributionInvitation. +""" +type CreateAttributionInvitationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner scoping the reattributable data. + """ + owner: Organization + + """ + The account owning the data to reattribute. + """ + source: Claimable + + """ + The account which may claim the data. + """ + target: Claimable +} + +""" +Autogenerated input type of CreateBranchProtectionRule +""" +input CreateBranchProtectionRuleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the repository in which a new branch protection rule should be created in. + """ + repositoryId: ID! + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String! + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Can admins override branch protection. + """ + isAdminEnforced: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User, Team, or App IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass pull requests targeting matching branches. + """ + bypassPullRequestActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass force push targeting matching branches. + """ + bypassForcePushActorIds: [ID!] + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + A list of User, Team, or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + The list of required status checks + """ + requiredStatusChecks: [RequiredStatusCheckInput!] + + """ + Are successful deployments required before merging. + """ + requiresDeployments: Boolean + + """ + The list of required deployment environments + """ + requiredDeploymentEnvironments: [String!] + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean + + """ + Whether users can pull changes from upstream when the branch is locked. Set to `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean +} + +""" +Autogenerated return type of CreateBranchProtectionRule. +""" +type CreateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckRun +""" +input CreateCheckRunInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the repository. + """ + repositoryId: ID! + + """ + The name of the check. + """ + name: String! + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The current status. + """ + status: RequestableCheckStatusState + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] +} + +""" +Autogenerated return type of CreateCheckRun. +""" +type CreateCheckRunPayload { + """ + The newly created check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCheckSuite +""" +input CreateCheckSuiteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + The SHA of the head commit. + """ + headSha: GitObjectID! +} + +""" +Autogenerated return type of CreateCheckSuite. +""" +type CreateCheckSuitePayload { + """ + The newly created check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of CreateCommitOnBranch +""" +input CreateCommitOnBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Ref to be updated. Must be a branch. + """ + branch: CommittableBranch! + + """ + A description of changes to files in this commit. + """ + fileChanges: FileChanges + + """ + The commit message the be included with the commit. + """ + message: CommitMessage! + + """ + The git commit oid expected at the head of the branch prior to the commit + """ + expectedHeadOid: GitObjectID! +} + +""" +Autogenerated return type of CreateCommitOnBranch. +""" +type CreateCommitOnBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new commit. + """ + commit: Commit + + """ + The ref which has been updated to point to the new commit. + """ + ref: Ref +} + +""" +Autogenerated input type of CreateDeployment +""" +input CreateDeploymentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the repository. + """ + repositoryId: ID! + + """ + The node ID of the ref to be deployed. + """ + refId: ID! + + """ + Attempt to automatically merge the default branch into the requested ref, defaults to true. + """ + autoMerge: Boolean = true + + """ + The status contexts to verify against commit status checks. To bypass required contexts, pass an empty array. Defaults to all unique contexts. + """ + requiredContexts: [String!] + + """ + Short description of the deployment. + """ + description: String = "" + + """ + Name for the target deployment environment. + """ + environment: String = "production" + + """ + Specifies a task to execute. + """ + task: String = "deploy" + + """ + JSON payload with extra information about the deployment. + """ + payload: String = "{}" +} + +""" +Autogenerated return type of CreateDeployment. +""" +type CreateDeploymentPayload { + """ + True if the default branch has been auto-merged into the deployment ref. + """ + autoMerged: Boolean + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment. + """ + deployment: Deployment +} + +""" +Autogenerated input type of CreateDeploymentStatus +""" +input CreateDeploymentStatusInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the deployment. + """ + deploymentId: ID! + + """ + The state of the deployment. + """ + state: DeploymentStatusState! + + """ + A short description of the status. Maximum length of 140 characters. + """ + description: String = "" + + """ + If provided, updates the environment of the deploy. Otherwise, does not modify the environment. + """ + environment: String + + """ + Sets the URL for accessing your environment. + """ + environmentUrl: String = "" + + """ + Adds a new inactive status to all non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. + """ + autoInactive: Boolean = true + + """ + The log URL to associate with this status. This URL should contain output to keep the user updated while the task is running or serve as historical information for what happened in the deployment. + """ + logUrl: String = "" +} + +""" +Autogenerated return type of CreateDeploymentStatus. +""" +type CreateDeploymentStatusPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new deployment status. + """ + deploymentStatus: DeploymentStatus +} + +""" +Autogenerated input type of CreateDiscussion +""" +input CreateDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the repository on which to create the discussion. + """ + repositoryId: ID! + + """ + The title of the discussion. + """ + title: String! + + """ + The body of the discussion. + """ + body: String! + + """ + The id of the discussion category to associate with this discussion. + """ + categoryId: ID! +} + +""" +Autogenerated return type of CreateDiscussion. +""" +type CreateDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was just created. + """ + discussion: Discussion +} + +""" +Autogenerated input type of CreateEnterpriseOrganization +""" +input CreateEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise owning the new organization. + """ + enterpriseId: ID! + + """ + The login of the new organization. + """ + login: String! + + """ + The profile name of the new organization. + """ + profileName: String! + + """ + The email used for sending billing receipts. + """ + billingEmail: String! + + """ + The logins for the administrators of the new organization. + """ + adminLogins: [String!]! +} + +""" +Autogenerated return type of CreateEnterpriseOrganization. +""" +type CreateEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise that owns the created organization. + """ + enterprise: Enterprise + + """ + The organization that was created. + """ + organization: Organization +} + +""" +Autogenerated input type of CreateEnvironment +""" +input CreateEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the repository. + """ + repositoryId: ID! + + """ + The name of the environment. + """ + name: String! +} + +""" +Autogenerated return type of CreateEnvironment. +""" +type CreateEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new or existing environment. + """ + environment: Environment +} + +""" +Autogenerated input type of CreateIpAllowListEntry +""" +input CreateIpAllowListEntryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner for which to create the new IP allow list entry. + """ + ownerId: ID! + + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + An optional name for the IP allow list entry. + """ + name: String + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! +} + +""" +Autogenerated return type of CreateIpAllowListEntry. +""" +type CreateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was created. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of CreateIssue +""" +input CreateIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + The title for the issue. + """ + title: String! + + """ + The body for the issue description. + """ + body: String + + """ + The Node ID for the user assignee for this issue. + """ + assigneeIds: [ID!] + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] + + """ + The name of an issue template in the repository, assigns labels and assignees from the template to the issue + """ + issueTemplate: String +} + +""" +Autogenerated return type of CreateIssue. +""" +type CreateIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new issue. + """ + issue: Issue +} + +""" +Autogenerated input type of CreateLabel +""" +input CreateLabelInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + A 6 character hex code, without the leading #, identifying the color of the label. + """ + color: String! + + """ + The name of the label. + """ + name: String! + + """ + A brief description of the label, such as its purpose. + """ + description: String +} + +""" +Autogenerated return type of CreateLabel. +""" +type CreateLabelPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new label. + """ + label: Label +} + +""" +Autogenerated input type of CreateLinkedBranch +""" +input CreateLinkedBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to link to. + """ + issueId: ID! + + """ + The commit SHA to base the new branch on. + """ + oid: GitObjectID! + + """ + The name of the new branch. Defaults to issue number and title. + """ + name: String + + """ + ID of the repository to create the branch in. Defaults to the issue repository. + """ + repositoryId: ID +} + +""" +Autogenerated return type of CreateLinkedBranch. +""" +type CreateLinkedBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was linked to. + """ + issue: Issue + + """ + The new branch issue reference. + """ + linkedBranch: LinkedBranch +} + +""" +Autogenerated input type of CreateMigrationSource +""" +input CreateMigrationSourceInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The migration source name. + """ + name: String! + + """ + The migration source URL, for example `https://github.com` or `https://monalisa.ghe.com`. + """ + url: String + + """ + The migration source access token. + """ + accessToken: String + + """ + The migration source type. + """ + type: MigrationSourceType! + + """ + The ID of the organization that will own the migration source. + """ + ownerId: ID! + + """ + The GitHub personal access token of the user importing to the target repository. + """ + githubPat: String +} + +""" +Autogenerated return type of CreateMigrationSource. +""" +type CreateMigrationSourcePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created migration source. + """ + migrationSource: MigrationSource +} + +""" +Autogenerated input type of CreateProject +""" +input CreateProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner ID to create the project under. + """ + ownerId: ID! + + """ + The name of project. + """ + name: String! + + """ + The description of project. + """ + body: String + + """ + The name of the GitHub-provided template. + """ + template: ProjectTemplate + + """ + A list of repository IDs to create as linked repositories for the project + """ + repositoryIds: [ID!] +} + +""" +Autogenerated return type of CreateProject. +""" +type CreateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new project. + """ + project: Project +} + +""" +Autogenerated input type of CreateProjectV2Field +""" +input CreateProjectV2FieldInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to create the field in. + """ + projectId: ID! + + """ + The data type of the field. + """ + dataType: ProjectV2CustomFieldType! + + """ + The name of the field. + """ + name: String! + + """ + Options for a single select field. At least one value is required if data_type is SINGLE_SELECT + """ + singleSelectOptions: [ProjectV2SingleSelectFieldOptionInput!] +} + +""" +Autogenerated return type of CreateProjectV2Field. +""" +type CreateProjectV2FieldPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new field. + """ + projectV2Field: ProjectV2FieldConfiguration +} + +""" +Autogenerated input type of CreateProjectV2 +""" +input CreateProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner ID to create the project under. + """ + ownerId: ID! + + """ + The title of the project. + """ + title: String! + + """ + The repository to link the project to. + """ + repositoryId: ID + + """ + The team to link the project to. The team will be granted read permissions. + """ + teamId: ID +} + +""" +Autogenerated return type of CreateProjectV2. +""" +type CreateProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of CreateProjectV2StatusUpdate +""" +input CreateProjectV2StatusUpdateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to create the status update in. + """ + projectId: ID! + + """ + The start date of the status update. + """ + startDate: Date + + """ + The target date of the status update. + """ + targetDate: Date + + """ + The status of the status update. + """ + status: ProjectV2StatusUpdateStatus + + """ + The body of the status update. + """ + body: String +} + +""" +Autogenerated return type of CreateProjectV2StatusUpdate. +""" +type CreateProjectV2StatusUpdatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The status update updated in the project. + """ + statusUpdate: ProjectV2StatusUpdate +} + +""" +Autogenerated input type of CreatePullRequest +""" +input CreatePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. You cannot update the base branch on a pull request to point + to another repository. + """ + baseRefName: String! + + """ + The name of the branch where your changes are implemented. For cross-repository pull requests + in the same network, namespace `head_ref_name` with a user like this: `username:branch`. + """ + headRefName: String! + + """ + The Node ID of the head repository. + """ + headRepositoryId: ID + + """ + The title of the pull request. + """ + title: String! + + """ + The contents of the pull request. + """ + body: String + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean = true + + """ + Indicates whether this pull request should be a draft. + """ + draft: Boolean = false +} + +""" +Autogenerated return type of CreatePullRequest. +""" +type CreatePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of CreateRef +""" +input CreateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Repository to create the Ref in. + """ + repositoryId: ID! + + """ + The fully qualified name of the new Ref (ie: `refs/heads/my_new_branch`). + """ + name: String! + + """ + The GitObjectID that the new Ref shall target. Must point to a commit. + """ + oid: GitObjectID! +} + +""" +Autogenerated return type of CreateRef. +""" +type CreateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created ref. + """ + ref: Ref +} + +""" +Autogenerated input type of CreateRepository +""" +input CreateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the new repository. + """ + name: String! + + """ + The ID of the owner for the new repository. + """ + ownerId: ID + + """ + A short description of the new repository. + """ + description: String + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! + + """ + Whether this repository should be marked as a template such that anyone who can access it can create new repositories with the same files and directory structure. + """ + template: Boolean = false + + """ + The URL for a web page about this repository. + """ + homepageUrl: URI + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean = false + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean = true + + """ + When an organization is specified as the owner, this ID identifies the team that should be granted access to the new repository. + """ + teamId: ID +} + +""" +Autogenerated return type of CreateRepository. +""" +type CreateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository. + """ + repository: Repository +} + +""" +Autogenerated input type of CreateRepositoryRuleset +""" +input CreateRepositoryRulesetInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the source in which a new ruleset should be created in. + """ + sourceId: ID! + + """ + The name of the ruleset. + """ + name: String! + + """ + The target of the ruleset. + """ + target: RepositoryRulesetTarget + + """ + The list of rules for this ruleset + """ + rules: [RepositoryRuleInput!] + + """ + The set of conditions for this ruleset + """ + conditions: RepositoryRuleConditionsInput! + + """ + The enforcement level for this ruleset + """ + enforcement: RuleEnforcement! + + """ + A list of actors that are allowed to bypass rules in this ruleset. + """ + bypassActors: [RepositoryRulesetBypassActorInput!] +} + +""" +Autogenerated return type of CreateRepositoryRuleset. +""" +type CreateRepositoryRulesetPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created Ruleset. + """ + ruleset: RepositoryRuleset +} + +""" +Autogenerated input type of CreateSponsorsListing +""" +input CreateSponsorsListingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The username of the organization to create a GitHub Sponsors profile for, if desired. Defaults to creating a GitHub Sponsors profile for the authenticated user if omitted. + """ + sponsorableLogin: String + + """ + The username of the supported fiscal host's GitHub organization, if you want to receive sponsorship payouts through a fiscal host rather than directly to a bank account. For example, 'Open-Source-Collective' for Open Source Collective or 'numfocus' for numFOCUS. Case insensitive. See https://docs.github.com/sponsors/receiving-sponsorships-through-github-sponsors/using-a-fiscal-host-to-receive-github-sponsors-payouts for more information. + """ + fiscalHostLogin: String + + """ + The URL for your profile page on the fiscal host's website, e.g., https://opencollective.com/babel or https://numfocus.org/project/bokeh. Required if fiscalHostLogin is specified. + """ + fiscallyHostedProjectProfileUrl: String + + """ + The country or region where the sponsorable's bank account is located. Required if fiscalHostLogin is not specified, ignored when fiscalHostLogin is specified. + """ + billingCountryOrRegionCode: SponsorsCountryOrRegionCode + + """ + The country or region where the sponsorable resides. This is for tax purposes. Required if the sponsorable is yourself, ignored when sponsorableLogin specifies an organization. + """ + residenceCountryOrRegionCode: SponsorsCountryOrRegionCode + + """ + The email address we should use to contact you about the GitHub Sponsors profile being created. This will not be shared publicly. Must be a verified email address already on your GitHub account. Only relevant when the sponsorable is yourself. Defaults to your primary email address on file if omitted. + """ + contactEmail: String + + """ + Provide an introduction to serve as the main focus that appears on your GitHub Sponsors profile. It's a great opportunity to help potential sponsors learn more about you, your work, and why their sponsorship is important to you. GitHub-flavored Markdown is supported. + """ + fullDescription: String +} + +""" +Autogenerated return type of CreateSponsorsListing. +""" +type CreateSponsorsListingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new GitHub Sponsors profile. + """ + sponsorsListing: SponsorsListing +} + +""" +Autogenerated input type of CreateSponsorsTier +""" +input CreateSponsorsTierInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user or organization who owns the GitHub Sponsors profile. Defaults to the current user if omitted and sponsorableLogin is not given. + """ + sponsorableId: ID + + """ + The username of the user or organization who owns the GitHub Sponsors profile. Defaults to the current user if omitted and sponsorableId is not given. + """ + sponsorableLogin: String + + """ + The value of the new tier in US dollars. Valid values: 1-12000. + """ + amount: Int! + + """ + Whether sponsorships using this tier should happen monthly/yearly or just once. + """ + isRecurring: Boolean = true + + """ + Optional ID of the private repository that sponsors at this tier should gain read-only access to. Must be owned by an organization. + """ + repositoryId: ID + + """ + Optional login of the organization owner of the private repository that sponsors at this tier should gain read-only access to. Necessary if repositoryName is given. Will be ignored if repositoryId is given. + """ + repositoryOwnerLogin: String + + """ + Optional name of the private repository that sponsors at this tier should gain read-only access to. Must be owned by an organization. Necessary if repositoryOwnerLogin is given. Will be ignored if repositoryId is given. + """ + repositoryName: String + + """ + Optional message new sponsors at this tier will receive. + """ + welcomeMessage: String + + """ + A description of what this tier is, what perks sponsors might receive, what a sponsorship at this tier means for you, etc. + """ + description: String! + + """ + Whether to make the tier available immediately for sponsors to choose. Defaults to creating a draft tier that will not be publicly visible. + """ + publish: Boolean = false +} + +""" +Autogenerated return type of CreateSponsorsTier. +""" +type CreateSponsorsTierPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new tier. + """ + sponsorsTier: SponsorsTier +} + +""" +Autogenerated input type of CreateSponsorship +""" +input CreateSponsorshipInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID + + """ + The username of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String + + """ + The ID of one of sponsorable's existing tiers to sponsor at. Required if amount is not specified. + """ + tierId: ID + + """ + The amount to pay to the sponsorable in US dollars. Required if a tierId is not specified. Valid values: 1-12000. + """ + amount: Int + + """ + Whether the sponsorship should happen monthly/yearly or just this one time. Required if a tierId is not specified. + """ + isRecurring: Boolean + + """ + Whether the sponsor should receive email updates from the sponsorable. + """ + receiveEmails: Boolean = true + + """ + Specify whether others should be able to see that the sponsor is sponsoring the sponsorable. Public visibility still does not reveal which tier is used. + """ + privacyLevel: SponsorshipPrivacy = PUBLIC +} + +""" +Autogenerated return type of CreateSponsorship. +""" +type CreateSponsorshipPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The sponsorship that was started. + """ + sponsorship: Sponsorship +} + +""" +Autogenerated input type of CreateSponsorships +""" +input CreateSponsorshipsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The username of the user or organization who is acting as the sponsor, paying for the sponsorships. + """ + sponsorLogin: String! + + """ + The list of maintainers to sponsor and for how much apiece. + """ + sponsorships: [BulkSponsorship!]! + + """ + Whether the sponsor should receive email updates from the sponsorables. + """ + receiveEmails: Boolean = false + + """ + Specify whether others should be able to see that the sponsor is sponsoring the sponsorables. Public visibility still does not reveal the dollar value of the sponsorship. + """ + privacyLevel: SponsorshipPrivacy = PUBLIC + + """ + Whether the sponsorships created should continue each billing cycle for the sponsor (monthly or annually), versus lasting only a single month. Defaults to one-time sponsorships. + """ + recurring: Boolean = false +} + +""" +Autogenerated return type of CreateSponsorships. +""" +type CreateSponsorshipsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The users and organizations who received a sponsorship. + """ + sponsorables: [Sponsorable!] +} + +""" +Autogenerated input type of CreateTeamDiscussionComment +""" +input CreateTeamDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the discussion to which the comment belongs. This field is required. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `discussionId` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + discussionId: ID + + """ + The content of the comment. This field is required. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `body` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + body: String +} + +""" +Autogenerated return type of CreateTeamDiscussionComment. +""" +type CreateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new comment. + """ + teamDiscussionComment: TeamDiscussionComment @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") +} + +""" +Autogenerated input type of CreateTeamDiscussion +""" +input CreateTeamDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the team to which the discussion belongs. This field is required. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `teamId` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + teamId: ID + + """ + The title of the discussion. This field is required. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `title` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + title: String + + """ + The content of the discussion. This field is required. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `body` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + body: String + + """ + If true, restricts the visibility of this discussion to team members and organization owners. If false or not specified, allows any organization member to view this discussion. + + **Upcoming Change on 2024-07-01 UTC** + **Description:** `private` will be removed. Follow the guide at https://github.blog/changelog/2023-02-08-sunset-notice-team-discussions/ to find a suitable replacement. + **Reason:** The Team Discussions feature is deprecated in favor of Organization Discussions. + """ + private: Boolean +} + +""" +Autogenerated return type of CreateTeamDiscussion. +""" +type CreateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new discussion. + """ + teamDiscussion: TeamDiscussion @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") +} + +""" +Autogenerated input type of CreateUserList +""" +input CreateUserListInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the new list + """ + name: String! + + """ + A description of the list + """ + description: String + + """ + Whether or not the list is private + """ + isPrivate: Boolean = false +} + +""" +Autogenerated return type of CreateUserList. +""" +type CreateUserListPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The list that was just created + """ + list: UserList + + """ + The user who created the list + """ + viewer: User +} + +""" +Represents the contribution a user made by committing to a repository. +""" +type CreatedCommitContribution implements Contribution { + """ + How many commits were made on this day to this repository by the user. + """ + commitCount: Int! + + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository the user made a commit in. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedCommitContribution. +""" +type CreatedCommitContributionConnection { + """ + A list of edges. + """ + edges: [CreatedCommitContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedCommitContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of commits across days and repositories in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedCommitContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedCommitContribution +} + +""" +Represents the contribution a user made on GitHub by opening an issue. +""" +type CreatedIssueContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + The issue that was opened. + """ + issue: Issue! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedIssueContribution. +""" +type CreatedIssueContributionConnection { + """ + A list of edges. + """ + edges: [CreatedIssueContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedIssueContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedIssueContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedIssueContribution +} + +""" +Represents either a issue the viewer can access or a restricted contribution. +""" +union CreatedIssueOrRestrictedContribution = CreatedIssueContribution|RestrictedContribution + +""" +Represents the contribution a user made on GitHub by opening a pull request. +""" +type CreatedPullRequestContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request that was opened. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestContribution. +""" +type CreatedPullRequestContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestContribution +} + +""" +Represents either a pull request the viewer can access or a restricted contribution. +""" +union CreatedPullRequestOrRestrictedContribution = CreatedPullRequestContribution|RestrictedContribution + +""" +Represents the contribution a user made by leaving a review on a pull request. +""" +type CreatedPullRequestReviewContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The pull request the user reviewed. + """ + pullRequest: PullRequest! + + """ + The review the user left on the pull request. + """ + pullRequestReview: PullRequestReview! + + """ + The repository containing the pull request that the user reviewed. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedPullRequestReviewContribution. +""" +type CreatedPullRequestReviewContributionConnection { + """ + A list of edges. + """ + edges: [CreatedPullRequestReviewContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedPullRequestReviewContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedPullRequestReviewContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedPullRequestReviewContribution +} + +""" +Represents the contribution a user made on GitHub by creating a repository. +""" +type CreatedRepositoryContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The repository that was created. + """ + repository: Repository! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +The connection type for CreatedRepositoryContribution. +""" +type CreatedRepositoryContributionConnection { + """ + A list of edges. + """ + edges: [CreatedRepositoryContributionEdge] + + """ + A list of nodes. + """ + nodes: [CreatedRepositoryContribution] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type CreatedRepositoryContributionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: CreatedRepositoryContribution +} + +""" +Represents either a repository the viewer can access or a restricted contribution. +""" +union CreatedRepositoryOrRestrictedContribution = CreatedRepositoryContribution|RestrictedContribution + +""" +Represents a mention made by one issue or pull request to another. +""" +type CrossReferencedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the CrossReferencedEvent object + """ + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Identifies when the reference was made. + """ + referencedAt: DateTime! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + Issue or pull request that made the reference. + """ + source: ReferencedSubject! + + """ + Issue or pull request to which the reference was made. + """ + target: ReferencedSubject! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + Checks if the target will be closed when the source is merged. + """ + willCloseTarget: Boolean! +} + +""" +An ISO-8601 encoded date string. +""" +scalar Date + +""" +An ISO-8601 encoded UTC date string. +""" +scalar DateTime + +""" +Autogenerated input type of DeclineTopicSuggestion +""" +input DeclineTopicSuggestionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + + **Upcoming Change on 2024-04-01 UTC** + **Description:** `repositoryId` will be removed. + **Reason:** Suggested topics are no longer supported + """ + repositoryId: ID + + """ + The name of the suggested topic. + + **Upcoming Change on 2024-04-01 UTC** + **Description:** `name` will be removed. + **Reason:** Suggested topics are no longer supported + """ + name: String + + """ + The reason why the suggested topic is declined. + + **Upcoming Change on 2024-04-01 UTC** + **Description:** `reason` will be removed. + **Reason:** Suggested topics are no longer supported + """ + reason: TopicSuggestionDeclineReason +} + +""" +Autogenerated return type of DeclineTopicSuggestion. +""" +type DeclineTopicSuggestionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The declined topic. + """ + topic: Topic @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") +} + +""" +The possible base permissions for repositories. +""" +enum DefaultRepositoryPermissionField { + """ + No access + """ + NONE + + """ + Can read repos by default + """ + READ + + """ + Can read and write repos by default + """ + WRITE + + """ + Can read, write, and administrate repos by default + """ + ADMIN +} + +""" +Entities that can be deleted. +""" +interface Deletable { + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! +} + +""" +Autogenerated input type of DeleteBranchProtectionRule +""" +input DeleteBranchProtectionRuleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the branch protection rule to be deleted. + """ + branchProtectionRuleId: ID! +} + +""" +Autogenerated return type of DeleteBranchProtectionRule. +""" +type DeleteBranchProtectionRulePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteDeployment +""" +input DeleteDeploymentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the deployment to be deleted. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteDeployment. +""" +type DeleteDeploymentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteDiscussionComment +""" +input DeleteDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node id of the discussion comment to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteDiscussionComment. +""" +type DeleteDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion comment that was just deleted. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of DeleteDiscussion +""" +input DeleteDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the discussion to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteDiscussion. +""" +type DeleteDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was just deleted. + """ + discussion: Discussion +} + +""" +Autogenerated input type of DeleteEnvironment +""" +input DeleteEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the environment to be deleted. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteEnvironment. +""" +type DeleteEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIpAllowListEntry +""" +input DeleteIpAllowListEntryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to delete. + """ + ipAllowListEntryId: ID! +} + +""" +Autogenerated return type of DeleteIpAllowListEntry. +""" +type DeleteIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was deleted. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of DeleteIssueComment +""" +input DeleteIssueCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteIssueComment. +""" +type DeleteIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteIssue +""" +input DeleteIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to delete. + """ + issueId: ID! +} + +""" +Autogenerated return type of DeleteIssue. +""" +type DeleteIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the issue belonged to + """ + repository: Repository +} + +""" +Autogenerated input type of DeleteLabel +""" +input DeleteLabelInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the label to be deleted. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteLabel. +""" +type DeleteLabelPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteLinkedBranch +""" +input DeleteLinkedBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the linked branch + """ + linkedBranchId: ID! +} + +""" +Autogenerated return type of DeleteLinkedBranch. +""" +type DeleteLinkedBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue the linked branch was unlinked from. + """ + issue: Issue +} + +""" +Autogenerated input type of DeletePackageVersion +""" +input DeletePackageVersionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the package version to be deleted. + """ + packageVersionId: ID! +} + +""" +Autogenerated return type of DeletePackageVersion. +""" +type DeletePackageVersionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Whether or not the operation succeeded. + """ + success: Boolean +} + +""" +Autogenerated input type of DeleteProjectCard +""" +input DeleteProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the card to delete. + """ + cardId: ID! +} + +""" +Autogenerated return type of DeleteProjectCard. +""" +type DeleteProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The column the deleted card was in. + """ + column: ProjectColumn + + """ + The deleted card ID. + """ + deletedCardId: ID +} + +""" +Autogenerated input type of DeleteProjectColumn +""" +input DeleteProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to delete. + """ + columnId: ID! +} + +""" +Autogenerated return type of DeleteProjectColumn. +""" +type DeleteProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted column ID. + """ + deletedColumnId: ID + + """ + The project the deleted column was in. + """ + project: Project +} + +""" +Autogenerated input type of DeleteProject +""" +input DeleteProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Project ID to update. + """ + projectId: ID! +} + +""" +Autogenerated return type of DeleteProject. +""" +type DeleteProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository or organization the project was removed from. + """ + owner: ProjectOwner +} + +""" +Autogenerated input type of DeleteProjectV2Field +""" +input DeleteProjectV2FieldInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the field to delete. + """ + fieldId: ID! +} + +""" +Autogenerated return type of DeleteProjectV2Field. +""" +type DeleteProjectV2FieldPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted field. + """ + projectV2Field: ProjectV2FieldConfiguration +} + +""" +Autogenerated input type of DeleteProjectV2 +""" +input DeleteProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to delete. + """ + projectId: ID! +} + +""" +Autogenerated input type of DeleteProjectV2Item +""" +input DeleteProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project from which the item should be removed. + """ + projectId: ID! + + """ + The ID of the item to be removed. + """ + itemId: ID! +} + +""" +Autogenerated return type of DeleteProjectV2Item. +""" +type DeleteProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the deleted item. + """ + deletedItemId: ID +} + +""" +Autogenerated return type of DeleteProjectV2. +""" +type DeleteProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted Project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of DeleteProjectV2StatusUpdate +""" +input DeleteProjectV2StatusUpdateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the status update to be removed. + """ + statusUpdateId: ID! +} + +""" +Autogenerated return type of DeleteProjectV2StatusUpdate. +""" +type DeleteProjectV2StatusUpdatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the deleted status update. + """ + deletedStatusUpdateId: ID + + """ + The project the deleted status update was in. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of DeleteProjectV2Workflow +""" +input DeleteProjectV2WorkflowInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the workflow to be removed. + """ + workflowId: ID! +} + +""" +Autogenerated return type of DeleteProjectV2Workflow. +""" +type DeleteProjectV2WorkflowPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the deleted workflow. + """ + deletedWorkflowId: ID + + """ + The project the deleted workflow was in. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of DeletePullRequestReviewComment +""" +input DeletePullRequestReviewCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeletePullRequestReviewComment. +""" +type DeletePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request review the deleted comment belonged to. + """ + pullRequestReview: PullRequestReview + + """ + The deleted pull request review comment. + """ + pullRequestReviewComment: PullRequestReviewComment +} + +""" +Autogenerated input type of DeletePullRequestReview +""" +input DeletePullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to delete. + """ + pullRequestReviewId: ID! +} + +""" +Autogenerated return type of DeletePullRequestReview. +""" +type DeletePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The deleted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of DeleteRef +""" +input DeleteRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Ref to be deleted. + """ + refId: ID! +} + +""" +Autogenerated return type of DeleteRef. +""" +type DeleteRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteRepositoryRuleset +""" +input DeleteRepositoryRulesetInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the repository ruleset to be deleted. + """ + repositoryRulesetId: ID! +} + +""" +Autogenerated return type of DeleteRepositoryRuleset. +""" +type DeleteRepositoryRulesetPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussionComment +""" +input DeleteTeamDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteTeamDiscussionComment. +""" +type DeleteTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteTeamDiscussion +""" +input DeleteTeamDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion ID to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteTeamDiscussion. +""" +type DeleteTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of DeleteUserList +""" +input DeleteUserListInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the list to delete. + """ + listId: ID! +} + +""" +Autogenerated return type of DeleteUserList. +""" +type DeleteUserListPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner of the list that will be deleted + """ + user: User +} + +""" +Autogenerated input type of DeleteVerifiableDomain +""" +input DeleteVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to delete. + """ + id: ID! +} + +""" +Autogenerated return type of DeleteVerifiableDomain. +""" +type DeleteVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owning account from which the domain was deleted. + """ + owner: VerifiableDomainOwner +} + +""" +Represents a 'demilestoned' event on a given issue or pull request. +""" +type DemilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the DemilestonedEvent object + """ + id: ID! + + """ + Identifies the milestone title associated with the 'demilestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +A Dependabot Update for a dependency in a repository +""" +type DependabotUpdate implements RepositoryNode { + """ + The error from a dependency update + """ + error: DependabotUpdateError + + """ + The associated pull request + """ + pullRequest: PullRequest + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +An error produced from a Dependabot Update +""" +type DependabotUpdateError { + """ + The body of the error + """ + body: String! + + """ + The error code + """ + errorType: String! + + """ + The title of the error + """ + title: String! +} + +""" +A dependency manifest entry +""" +type DependencyGraphDependency { + """ + Does the dependency itself have dependencies? + """ + hasDependencies: Boolean! + + """ + The original name of the package, as it appears in the manifest. + """ + packageLabel: String! @deprecated(reason: "`packageLabel` will be removed. Use normalized `packageName` field instead. Removal on 2022-10-01 UTC.") + + """ + The dependency package manager + """ + packageManager: String + + """ + The name of the package in the canonical form used by the package manager. + """ + packageName: String! + + """ + The repository containing the package + """ + repository: Repository + + """ + The dependency version requirements + """ + requirements: String! +} + +""" +The connection type for DependencyGraphDependency. +""" +type DependencyGraphDependencyConnection { + """ + A list of edges. + """ + edges: [DependencyGraphDependencyEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphDependency] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphDependencyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphDependency +} + +""" +The possible ecosystems of a dependency graph package. +""" +enum DependencyGraphEcosystem { + """ + Ruby gems hosted at RubyGems.org + """ + RUBYGEMS + + """ + JavaScript packages hosted at npmjs.com + """ + NPM + + """ + Python packages hosted at PyPI.org + """ + PIP + + """ + Java artifacts hosted at the Maven central repository + """ + MAVEN + + """ + .NET packages hosted at the NuGet Gallery + """ + NUGET + + """ + PHP packages hosted at packagist.org + """ + COMPOSER + + """ + Go modules + """ + GO + + """ + GitHub Actions + """ + ACTIONS + + """ + Rust crates + """ + RUST + + """ + Dart packages hosted at pub.dev + """ + PUB + + """ + Swift packages + """ + SWIFT +} + +""" +Dependency manifest for a repository +""" +type DependencyGraphManifest implements Node { + """ + Path to view the manifest file blob + """ + blobPath: String! + + """ + A list of manifest dependencies + """ + dependencies("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DependencyGraphDependencyConnection + + """ + The number of dependencies listed in the manifest + """ + dependenciesCount: Int + + """ + Is the manifest too big to parse? + """ + exceedsMaxSize: Boolean! + + """ + Fully qualified manifest filename + """ + filename: String! + + """ + The Node ID of the DependencyGraphManifest object + """ + id: ID! + + """ + Were we able to parse the manifest? + """ + parseable: Boolean! + + """ + The repository containing the manifest + """ + repository: Repository! +} + +""" +The connection type for DependencyGraphManifest. +""" +type DependencyGraphManifestConnection { + """ + A list of edges. + """ + edges: [DependencyGraphManifestEdge] + + """ + A list of nodes. + """ + nodes: [DependencyGraphManifest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DependencyGraphManifestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DependencyGraphManifest +} + +""" +A repository deploy key. +""" +type DeployKey implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the DeployKey object + """ + id: ID! + + """ + The deploy key. + """ + key: String! + + """ + Whether or not the deploy key is read only. + """ + readOnly: Boolean! + + """ + The deploy key title. + """ + title: String! + + """ + Whether or not the deploy key has been verified. + """ + verified: Boolean! +} + +""" +The connection type for DeployKey. +""" +type DeployKeyConnection { + """ + A list of edges. + """ + edges: [DeployKeyEdge] + + """ + A list of nodes. + """ + nodes: [DeployKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeployKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeployKey +} + +""" +Represents a 'deployed' event on a given pull request. +""" +type DeployedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment associated with the 'deployed' event. + """ + deployment: Deployment! + + """ + The Node ID of the DeployedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The ref associated with the 'deployed' event. + """ + ref: Ref +} + +""" +Represents triggered deployment instance. +""" +type Deployment implements Node { + """ + Identifies the commit sha of the deployment. + """ + commit: Commit + + """ + Identifies the oid of the deployment commit, even if the commit has been deleted. + """ + commitOid: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The deployment description. + """ + description: String + + """ + The latest environment to which this deployment was made. + """ + environment: String + + """ + The Node ID of the Deployment object + """ + id: ID! + + """ + The latest environment to which this deployment was made. + """ + latestEnvironment: String + + """ + The latest status of this deployment. + """ + latestStatus: DeploymentStatus + + """ + The original environment to which this deployment was made. + """ + originalEnvironment: String + + """ + Extra information that a deployment system might need. + """ + payload: String + + """ + Identifies the Ref of the deployment, if the deployment was created by ref. + """ + ref: Ref + + """ + Identifies the repository associated with the deployment. + """ + repository: Repository! + + """ + The current state of the deployment. + """ + state: DeploymentState + + """ + A list of statuses associated with the deployment. + """ + statuses("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentStatusConnection + + """ + The deployment task. + """ + task: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for Deployment. +""" +type DeploymentConnection { + """ + A list of edges. + """ + edges: [DeploymentEdge] + + """ + A list of nodes. + """ + nodes: [Deployment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Deployment +} + +""" +Represents a 'deployment_environment_changed' event on a given pull request. +""" +type DeploymentEnvironmentChangedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The deployment status that updated the deployment environment. + """ + deploymentStatus: DeploymentStatus! + + """ + The Node ID of the DeploymentEnvironmentChangedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Ordering options for deployment connections +""" +input DeploymentOrder { + """ + The field to order deployments by. + """ + field: DeploymentOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which deployment connections can be ordered. +""" +enum DeploymentOrderField { + """ + Order collection by creation time + """ + CREATED_AT +} + +""" +A protection rule. +""" +type DeploymentProtectionRule { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Whether deployments to this environment can be approved by the user who created the deployment. + """ + preventSelfReview: Boolean + + """ + The teams or users that can review the deployment + """ + reviewers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentReviewerConnection! + + """ + The timeout in minutes for this protection rule. + """ + timeout: Int! + + """ + The type of protection rule. + """ + type: DeploymentProtectionRuleType! +} + +""" +The connection type for DeploymentProtectionRule. +""" +type DeploymentProtectionRuleConnection { + """ + A list of edges. + """ + edges: [DeploymentProtectionRuleEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentProtectionRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentProtectionRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentProtectionRule +} + +""" +The possible protection rule types. +""" +enum DeploymentProtectionRuleType { + """ + Required reviewers + """ + REQUIRED_REVIEWERS + + """ + Wait timer + """ + WAIT_TIMER + + """ + Branch policy + """ + BRANCH_POLICY +} + +""" +A request to deploy a workflow run to an environment. +""" +type DeploymentRequest { + """ + Whether or not the current user can approve the deployment + """ + currentUserCanApprove: Boolean! + + """ + The target environment of the deployment + """ + environment: Environment! + + """ + The teams or users that can review the deployment + """ + reviewers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentReviewerConnection! + + """ + The wait timer in minutes configured in the environment + """ + waitTimer: Int! + + """ + The wait timer in minutes configured in the environment + """ + waitTimerStartedAt: DateTime +} + +""" +The connection type for DeploymentRequest. +""" +type DeploymentRequestConnection { + """ + A list of edges. + """ + edges: [DeploymentRequestEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentRequest +} + +""" +A deployment review. +""" +type DeploymentReview implements Node { + """ + The comment the user left. + """ + comment: String! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The environments approved or rejected + """ + environments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnvironmentConnection! + + """ + The Node ID of the DeploymentReview object + """ + id: ID! + + """ + The decision of the user. + """ + state: DeploymentReviewState! + + """ + The user that reviewed the deployment. + """ + user: User! +} + +""" +The connection type for DeploymentReview. +""" +type DeploymentReviewConnection { + """ + A list of edges. + """ + edges: [DeploymentReviewEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentReview] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentReviewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentReview +} + +""" +The possible states for a deployment review. +""" +enum DeploymentReviewState { + """ + The deployment was approved. + """ + APPROVED + + """ + The deployment was rejected. + """ + REJECTED +} + +""" +Users and teams. +""" +union DeploymentReviewer = Team|User + +""" +The connection type for DeploymentReviewer. +""" +type DeploymentReviewerConnection { + """ + A list of edges. + """ + edges: [DeploymentReviewerEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentReviewer] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentReviewerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentReviewer +} + +""" +The possible states in which a deployment can be. +""" +enum DeploymentState { + """ + The pending deployment was not updated after 30 minutes. + """ + ABANDONED + + """ + The deployment is currently active. + """ + ACTIVE + + """ + An inactive transient deployment. + """ + DESTROYED + + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment is pending. + """ + PENDING + + """ + The deployment was successful. + """ + SUCCESS + + """ + The deployment has queued + """ + QUEUED + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is waiting. + """ + WAITING +} + +""" +Describes the status of a given deployment attempt. +""" +type DeploymentStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who triggered the deployment. + """ + creator: Actor! + + """ + Identifies the deployment associated with status. + """ + deployment: Deployment! + + """ + Identifies the description of the deployment. + """ + description: String + + """ + Identifies the environment of the deployment at the time of this deployment status + """ + environment: String + + """ + Identifies the environment URL of the deployment. + """ + environmentUrl: URI + + """ + The Node ID of the DeploymentStatus object + """ + id: ID! + + """ + Identifies the log URL of the deployment. + """ + logUrl: URI + + """ + Identifies the current state of the deployment. + """ + state: DeploymentStatusState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for DeploymentStatus. +""" +type DeploymentStatusConnection { + """ + A list of edges. + """ + edges: [DeploymentStatusEdge] + + """ + A list of nodes. + """ + nodes: [DeploymentStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DeploymentStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DeploymentStatus +} + +""" +The possible states for a deployment status. +""" +enum DeploymentStatusState { + """ + The deployment is pending. + """ + PENDING + + """ + The deployment was successful. + """ + SUCCESS + + """ + The deployment has failed. + """ + FAILURE + + """ + The deployment is inactive. + """ + INACTIVE + + """ + The deployment experienced an error. + """ + ERROR + + """ + The deployment is queued + """ + QUEUED + + """ + The deployment is in progress. + """ + IN_PROGRESS + + """ + The deployment is waiting. + """ + WAITING +} + +""" +Autogenerated input type of DequeuePullRequest +""" +input DequeuePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the pull request to be dequeued. + """ + id: ID! +} + +""" +Autogenerated return type of DequeuePullRequest. +""" +type DequeuePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The merge queue entry of the dequeued pull request. + """ + mergeQueueEntry: MergeQueueEntry +} + +""" +The possible sides of a diff. +""" +enum DiffSide { + """ + The left side of the diff. + """ + LEFT + + """ + The right side of the diff. + """ + RIGHT +} + +""" +Autogenerated input type of DisablePullRequestAutoMerge +""" +input DisablePullRequestAutoMergeInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to disable auto merge on. + """ + pullRequestId: ID! +} + +""" +Autogenerated return type of DisablePullRequestAutoMerge. +""" +type DisablePullRequestAutoMergePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request auto merge was disabled on. + """ + pullRequest: PullRequest +} + +""" +Represents a 'disconnected' event on a given issue or pull request. +""" +type DisconnectedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the DisconnectedEvent object + """ + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Issue or pull request from which the issue was disconnected. + """ + source: ReferencedSubject! + + """ + Issue or pull request which was disconnected. + """ + subject: ReferencedSubject! +} + +""" +A discussion in a repository. +""" +type Discussion implements Closable & Comment & Deletable & Labelable & Lockable & Node & Reactable & RepositoryNode & Subscribable & Updatable & Votable { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + The comment chosen as this discussion's answer, if any. + """ + answer: DiscussionComment + + """ + The time when a user chose this discussion's answer, if answered. + """ + answerChosenAt: DateTime + + """ + The user who chose this discussion's answer, if answered. + """ + answerChosenBy: Actor + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The main text of the discussion post. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + The category for this discussion. + """ + category: DiscussionCategory! + + """ + Indicates if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + The replies to the discussion. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DiscussionCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the Discussion object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Only return answered/unanswered discussions + """ + isAnswered: Boolean + + """ + A list of labels associated with the object. + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + `true` if the object is locked + """ + locked: Boolean! + + """ + The number identifying this discussion within the repository. + """ + number: Int! + + """ + The poll associated with this discussion, if one exists. + """ + poll: DiscussionPoll + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The path for this discussion. + """ + resourcePath: URI! + + """ + Identifies the reason for the discussion's state. + """ + stateReason: DiscussionStateReason + + """ + The title of this discussion. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + The URL for this discussion. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A category for discussions in a repository. +""" +type DiscussionCategory implements Node & RepositoryNode { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + A description of this category. + """ + description: String + + """ + An emoji representing this category. + """ + emoji: String! + + """ + This category's emoji rendered as HTML. + """ + emojiHTML: HTML! + + """ + The Node ID of the DiscussionCategory object + """ + id: ID! + + """ + Whether or not discussions in this category support choosing an answer with the markDiscussionCommentAsAnswer mutation. + """ + isAnswerable: Boolean! + + """ + The name of this category. + """ + name: String! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The slug of this category. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for DiscussionCategory. +""" +type DiscussionCategoryConnection { + """ + A list of edges. + """ + edges: [DiscussionCategoryEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionCategory] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionCategoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionCategory +} + +""" +The possible reasons for closing a discussion. +""" +enum DiscussionCloseReason { + """ + The discussion has been resolved + """ + RESOLVED + + """ + The discussion is no longer relevant + """ + OUTDATED + + """ + The discussion is a duplicate of another + """ + DUPLICATE +} + +""" +A comment on a discussion. +""" +type DiscussionComment implements Comment & Deletable & Minimizable & Node & Reactable & Updatable & UpdatableComment & Votable { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The time when this replied-to comment was deleted + """ + deletedAt: DateTime + + """ + The discussion this comment was created in + """ + discussion: Discussion + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the DiscussionComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Has this comment been chosen as the answer of its discussion? + """ + isAnswer: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The threaded replies to this comment. + """ + replies("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DiscussionCommentConnection! + + """ + The discussion comment this comment is a reply to + """ + replyTo: DiscussionComment + + """ + The path for this discussion comment. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + The URL for this discussion comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can the current user mark this comment as an answer? + """ + viewerCanMarkAsAnswer: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Can the current user unmark this comment as an answer? + """ + viewerCanUnmarkAsAnswer: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! +} + +""" +The connection type for DiscussionComment. +""" +type DiscussionCommentConnection { + """ + A list of edges. + """ + edges: [DiscussionCommentEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionComment +} + +""" +The connection type for Discussion. +""" +type DiscussionConnection { + """ + A list of edges. + """ + edges: [DiscussionEdge] + + """ + A list of nodes. + """ + nodes: [Discussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Discussion +} + +""" +Ways in which lists of discussions can be ordered upon return. +""" +input DiscussionOrder { + """ + The field by which to order discussions. + """ + field: DiscussionOrderField! + + """ + The direction in which to order discussions by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which discussion connections can be ordered. +""" +enum DiscussionOrderField { + """ + Order discussions by creation time. + """ + CREATED_AT + + """ + Order discussions by most recent modification time. + """ + UPDATED_AT +} + +""" +A poll for a discussion. +""" +type DiscussionPoll implements Node { + """ + The discussion that this poll belongs to. + """ + discussion: Discussion + + """ + The Node ID of the DiscussionPoll object + """ + id: ID! + + """ + The options for this poll. + """ + options("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "How to order the options for the discussion poll." orderBy: DiscussionPollOptionOrder = { + field: AUTHORED_ORDER + direction: ASC + } + ): DiscussionPollOptionConnection + + """ + The question that is being asked by this poll. + """ + question: String! + + """ + The total number of votes that have been cast for this poll. + """ + totalVoteCount: Int! + + """ + Indicates if the viewer has permission to vote in this poll. + """ + viewerCanVote: Boolean! + + """ + Indicates if the viewer has voted for any option in this poll. + """ + viewerHasVoted: Boolean! +} + +""" +An option for a discussion poll. +""" +type DiscussionPollOption implements Node { + """ + The Node ID of the DiscussionPollOption object + """ + id: ID! + + """ + The text for this option. + """ + option: String! + + """ + The discussion poll that this option belongs to. + """ + poll: DiscussionPoll + + """ + The total number of votes that have been cast for this option. + """ + totalVoteCount: Int! + + """ + Indicates if the viewer has voted for this option in the poll. + """ + viewerHasVoted: Boolean! +} + +""" +The connection type for DiscussionPollOption. +""" +type DiscussionPollOptionConnection { + """ + A list of edges. + """ + edges: [DiscussionPollOptionEdge] + + """ + A list of nodes. + """ + nodes: [DiscussionPollOption] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type DiscussionPollOptionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: DiscussionPollOption +} + +""" +Ordering options for discussion poll option connections. +""" +input DiscussionPollOptionOrder { + """ + The field to order poll options by. + """ + field: DiscussionPollOptionOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which discussion poll option connections can be ordered. +""" +enum DiscussionPollOptionOrderField { + """ + Order poll options by the order that the poll author specified when creating the poll. + """ + AUTHORED_ORDER + + """ + Order poll options by the number of votes it has. + """ + VOTE_COUNT +} + +""" +The possible states of a discussion. +""" +enum DiscussionState { + """ + A discussion that is open + """ + OPEN + + """ + A discussion that has been closed + """ + CLOSED +} + +""" +The possible state reasons of a discussion. +""" +enum DiscussionStateReason { + """ + The discussion has been resolved + """ + RESOLVED + + """ + The discussion is no longer relevant + """ + OUTDATED + + """ + The discussion is a duplicate of another + """ + DUPLICATE + + """ + The discussion was reopened + """ + REOPENED +} + +""" +Autogenerated input type of DismissPullRequestReview +""" +input DismissPullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! + + """ + The contents of the pull request review dismissal message. + """ + message: String! +} + +""" +Autogenerated return type of DismissPullRequestReview. +""" +type DismissPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The dismissed pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +The possible reasons that a Dependabot alert was dismissed. +""" +enum DismissReason { + """ + A fix has already been started + """ + FIX_STARTED + + """ + No bandwidth to fix this + """ + NO_BANDWIDTH + + """ + Risk is tolerable to this project + """ + TOLERABLE_RISK + + """ + This alert is inaccurate or incorrect + """ + INACCURATE + + """ + Vulnerable code is not actually used + """ + NOT_USED +} + +""" +Autogenerated input type of DismissRepositoryVulnerabilityAlert +""" +input DismissRepositoryVulnerabilityAlertInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Dependabot alert ID to dismiss. + """ + repositoryVulnerabilityAlertId: ID! + + """ + The reason the Dependabot alert is being dismissed. + """ + dismissReason: DismissReason! +} + +""" +Autogenerated return type of DismissRepositoryVulnerabilityAlert. +""" +type DismissRepositoryVulnerabilityAlertPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Dependabot alert that was dismissed + """ + repositoryVulnerabilityAlert: RepositoryVulnerabilityAlert +} + +""" +A draft issue within a project. +""" +type DraftIssue implements Node { + """ + A list of users to assigned to this draft issue. + """ + assignees("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + The body of the draft issue. + """ + body: String! + + """ + The body of the draft issue rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body of the draft issue rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this draft issue. + """ + creator: Actor + + """ + The Node ID of the DraftIssue object + """ + id: ID! + + """ + List of items linked with the draft issue (currently draft issue can be linked to only one item). + """ + projectV2Items("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2ItemConnection! + + """ + Projects that link to this draft issue (currently draft issue can be linked to only one project). + """ + projectsV2("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + The title of the draft issue + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Specifies a review comment to be left with a Pull Request Review. +""" +input DraftPullRequestReviewComment { + """ + Path to the file being commented on. + """ + path: String! + + """ + Position in the file to leave a comment on. + """ + position: Int! + + """ + Body of the comment to leave. + """ + body: String! +} + +""" +Specifies a review comment thread to be left with a Pull Request Review. +""" +input DraftPullRequestReviewThread { + """ + Path to the file being commented on. + """ + path: String! + + """ + The line of the blob to which the thread refers. The end of the line range for multi-line comments. + """ + line: Int! + + """ + The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. + """ + side: DiffSide = RIGHT + + """ + The first line of the range to which the comment refers. + """ + startLine: Int + + """ + The side of the diff on which the start line resides. + """ + startSide: DiffSide = RIGHT + + """ + Body of the comment to leave. + """ + body: String! +} + +""" +Autogenerated input type of EnablePullRequestAutoMerge +""" +input EnablePullRequestAutoMergeInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to enable auto-merge on. + """ + pullRequestId: ID! + + """ + Commit headline to use for the commit when the PR is mergable; if omitted, a default message will be used. NOTE: when merging with a merge queue any input value for commit headline is ignored. + """ + commitHeadline: String + + """ + Commit body to use for the commit when the PR is mergable; if omitted, a default message will be used. NOTE: when merging with a merge queue any input value for commit message is ignored. + """ + commitBody: String + + """ + The merge method to use. If omitted, defaults to `MERGE`. NOTE: when merging with a merge queue any input value for merge method is ignored. + """ + mergeMethod: PullRequestMergeMethod = MERGE + + """ + The email address to associate with this merge. + """ + authorEmail: String + + """ + The expected head OID of the pull request. + """ + expectedHeadOid: GitObjectID +} + +""" +Autogenerated return type of EnablePullRequestAutoMerge. +""" +type EnablePullRequestAutoMergePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request auto-merge was enabled on. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of EnqueuePullRequest +""" +input EnqueuePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the pull request to enqueue. + """ + pullRequestId: ID! + + """ + Add the pull request to the front of the queue. + """ + jump: Boolean + + """ + The expected head OID of the pull request. + """ + expectedHeadOid: GitObjectID +} + +""" +Autogenerated return type of EnqueuePullRequest. +""" +type EnqueuePullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The merge queue entry for the enqueued pull request. + """ + mergeQueueEntry: MergeQueueEntry +} + +""" +An account to manage multiple organizations with consolidated policy and billing. +""" +type Enterprise implements AnnouncementBanner & Node { + """ + The text of the announcement + """ + announcement: String + + """ + The date the announcement was created + """ + announcementCreatedAt: DateTime + + """ + The expiration date of the announcement, if any + """ + announcementExpiresAt: DateTime + + """ + Whether the announcement can be dismissed by the user + """ + announcementUserDismissible: Boolean + + """ + A URL pointing to the enterprise's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The enterprise's billing email. + """ + billingEmail: String + + """ + Enterprise billing information visible to enterprise billing managers. + """ + billingInfo: EnterpriseBillingInfo + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the enterprise. + """ + description: String + + """ + The description of the enterprise as HTML. + """ + descriptionHTML: HTML! + + """ + The Node ID of the Enterprise object + """ + id: ID! + + """ + The location of the enterprise. + """ + location: String + + """ + A list of users who are members of this enterprise. + """ + members("Only return members within the organizations with these logins" organizationLogins: [String!], "The search string to look for." query: String, "Ordering options for members returned from the connection." orderBy: EnterpriseMemberOrder = { + field: LOGIN + direction: ASC + } + , "The role of the user in the enterprise organization or server." role: EnterpriseUserAccountMembershipRole, "Only return members within the selected GitHub Enterprise deployment" deployment: EnterpriseUserDeployment, "Only return members with this two-factor authentication status. Does not include members who only have an account on a GitHub Enterprise Server instance." hasTwoFactorEnabled: Boolean = null, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseMemberConnection! + + """ + The name of the enterprise. + """ + name: String! + + """ + A list of organizations that belong to this enterprise. + """ + organizations("The search string to look for." query: String, "The viewer's role in an organization." viewerOrganizationRole: RoleInOrganization, "Ordering options for organizations returned from the connection." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationConnection! + + """ + Enterprise information visible to enterprise owners or enterprise owners' personal access tokens (classic) with read:enterprise or admin:enterprise scope. + """ + ownerInfo: EnterpriseOwnerInfo + + """ + The raw content of the enterprise README. + """ + readme: String + + """ + The content of the enterprise README as HTML. + """ + readmeHTML: HTML! + + """ + The HTTP path for this enterprise. + """ + resourcePath: URI! + + """ + The URL-friendly identifier for the enterprise. + """ + slug: String! + + """ + The HTTP URL for this enterprise. + """ + url: URI! + + """ + Is the current viewer an admin of this enterprise? + """ + viewerIsAdmin: Boolean! + + """ + The URL of the enterprise website. + """ + websiteUrl: URI +} + +""" +The connection type for User. +""" +type EnterpriseAdministratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an administrator of an enterprise. +""" +type EnterpriseAdministratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +An invitation for a user to become an owner or billing manager of an enterprise. +""" +type EnterpriseAdministratorInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email of the person who was invited to the enterprise. + """ + email: String + + """ + The enterprise the invitation is for. + """ + enterprise: Enterprise! + + """ + The Node ID of the EnterpriseAdministratorInvitation object + """ + id: ID! + + """ + The user who was invited to the enterprise. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User + + """ + The invitee's pending role in the enterprise (owner or billing_manager). + """ + role: EnterpriseAdministratorRole! +} + +""" +The connection type for EnterpriseAdministratorInvitation. +""" +type EnterpriseAdministratorInvitationConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorInvitationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseAdministratorInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseAdministratorInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseAdministratorInvitation +} + +""" +Ordering options for enterprise administrator invitation connections +""" +input EnterpriseAdministratorInvitationOrder { + """ + The field to order enterprise administrator invitations by. + """ + field: EnterpriseAdministratorInvitationOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which enterprise administrator invitation connections can be ordered. +""" +enum EnterpriseAdministratorInvitationOrderField { + """ + Order enterprise administrator member invitations by creation time + """ + CREATED_AT +} + +""" +The possible administrator roles in an enterprise account. +""" +enum EnterpriseAdministratorRole { + """ + Represents an owner of the enterprise account. + """ + OWNER + + """ + Represents a billing manager of the enterprise account. + """ + BILLING_MANAGER +} + +""" +The possible values for the enterprise allow private repository forking policy value. +""" +enum EnterpriseAllowPrivateRepositoryForkingPolicyValue { + """ + Members can fork a repository to an organization within this enterprise. + """ + ENTERPRISE_ORGANIZATIONS + + """ + Members can fork a repository only within the same organization (intra-org). + """ + SAME_ORGANIZATION + + """ + Members can fork a repository to their user account or within the same organization. + """ + SAME_ORGANIZATION_USER_ACCOUNTS + + """ + Members can fork a repository to their enterprise-managed user account or an organization inside this enterprise. + """ + ENTERPRISE_ORGANIZATIONS_USER_ACCOUNTS + + """ + Members can fork a repository to their user account. + """ + USER_ACCOUNTS + + """ + Members can fork a repository to their user account or an organization, either inside or outside of this enterprise. + """ + EVERYWHERE +} + +""" +Metadata for an audit entry containing enterprise account information. +""" +interface EnterpriseAuditEntryData { + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI +} + +""" +Enterprise billing information visible to enterprise billing managers and owners. +""" +type EnterpriseBillingInfo { + """ + The number of licenseable users/emails across the enterprise. + """ + allLicensableUsersCount: Int! + + """ + The number of data packs used by all organizations owned by the enterprise. + """ + assetPacks: Int! + + """ + The bandwidth quota in GB for all organizations owned by the enterprise. + """ + bandwidthQuota: Float! + + """ + The bandwidth usage in GB for all organizations owned by the enterprise. + """ + bandwidthUsage: Float! + + """ + The bandwidth usage as a percentage of the bandwidth quota. + """ + bandwidthUsagePercentage: Int! + + """ + The storage quota in GB for all organizations owned by the enterprise. + """ + storageQuota: Float! + + """ + The storage usage in GB for all organizations owned by the enterprise. + """ + storageUsage: Float! + + """ + The storage usage as a percentage of the storage quota. + """ + storageUsagePercentage: Int! + + """ + The number of available licenses across all owned organizations based on the unique number of billable users. + """ + totalAvailableLicenses: Int! + + """ + The total number of licenses allocated. + """ + totalLicenses: Int! +} + +""" +The connection type for Enterprise. +""" +type EnterpriseConnection { + """ + A list of edges. + """ + edges: [EnterpriseEdge] + + """ + A list of nodes. + """ + nodes: [Enterprise] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible values for the enterprise base repository permission setting. +""" +enum EnterpriseDefaultRepositoryPermissionSettingValue { + """ + Organizations in the enterprise choose base repository permissions for their members. + """ + NO_POLICY + + """ + Organization members will be able to clone, pull, push, and add new collaborators to all organization repositories. + """ + ADMIN + + """ + Organization members will be able to clone, pull, and push all organization repositories. + """ + WRITE + + """ + Organization members will be able to clone and pull all organization repositories. + """ + READ + + """ + Organization members will only be able to clone and pull public repositories. + """ + NONE +} + +""" +An edge in a connection. +""" +type EnterpriseEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Enterprise +} + +""" +The possible values for an enabled/disabled enterprise setting. +""" +enum EnterpriseEnabledDisabledSettingValue { + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +The possible values for an enabled/no policy enterprise setting. +""" +enum EnterpriseEnabledSettingValue { + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + There is no policy set for organizations in the enterprise. + """ + NO_POLICY +} + +""" +The connection type for OrganizationInvitation. +""" +type EnterpriseFailedInvitationConnection { + """ + A list of edges. + """ + edges: [EnterpriseFailedInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the total count of unique users in the connection. + """ + totalUniqueUserCount: Int! +} + +""" +A failed invitation to be a member in an enterprise organization. +""" +type EnterpriseFailedInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +An identity provider configured to provision identities for an enterprise. Visible to enterprise owners or enterprise owners' personal access tokens (classic) with read:enterprise or admin:enterprise scope. +""" +type EnterpriseIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm + + """ + The enterprise this identity provider belongs to. + """ + enterprise: Enterprise + + """ + ExternalIdentities provisioned by this identity provider. + """ + externalIdentities("Filter to external identities with valid org membership only" membersOnly: Boolean, "Filter to external identities with the users login" login: String, "Filter to external identities with the users userName\/NameID attribute" userName: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ExternalIdentityConnection! + + """ + The Node ID of the EnterpriseIdentityProvider object + """ + id: ID! + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML identity provider. + """ + issuer: String + + """ + Recovery codes that can be used by admins to access the enterprise if the identity provider is unavailable. + """ + recoveryCodes: [String!] + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An object that is a member of an enterprise. +""" +union EnterpriseMember = EnterpriseUserAccount|User + +""" +The connection type for EnterpriseMember. +""" +type EnterpriseMemberConnection { + """ + A list of edges. + """ + edges: [EnterpriseMemberEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseMember] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is a member of an enterprise through one or more organizations. +""" +type EnterpriseMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseMember +} + +""" +An invitation for a user to become an unaffiliated member of an enterprise. +""" +type EnterpriseMemberInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email of the person who was invited to the enterprise. + """ + email: String + + """ + The enterprise the invitation is for. + """ + enterprise: Enterprise! + + """ + The Node ID of the EnterpriseMemberInvitation object + """ + id: ID! + + """ + The user who was invited to the enterprise. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User +} + +""" +The connection type for EnterpriseMemberInvitation. +""" +type EnterpriseMemberInvitationConnection { + """ + A list of edges. + """ + edges: [EnterpriseMemberInvitationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseMemberInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseMemberInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseMemberInvitation +} + +""" +Ordering options for enterprise administrator invitation connections +""" +input EnterpriseMemberInvitationOrder { + """ + The field to order enterprise member invitations by. + """ + field: EnterpriseMemberInvitationOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which enterprise member invitation connections can be ordered. +""" +enum EnterpriseMemberInvitationOrderField { + """ + Order enterprise member invitations by creation time + """ + CREATED_AT +} + +""" +Ordering options for enterprise member connections. +""" +input EnterpriseMemberOrder { + """ + The field to order enterprise members by. + """ + field: EnterpriseMemberOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which enterprise member connections can be ordered. +""" +enum EnterpriseMemberOrderField { + """ + Order enterprise members by login + """ + LOGIN + + """ + Order enterprise members by creation time + """ + CREATED_AT +} + +""" +The possible values for the enterprise members can create repositories setting. +""" +enum EnterpriseMembersCanCreateRepositoriesSettingValue { + """ + Organization owners choose whether to allow members to create repositories. + """ + NO_POLICY + + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will be able to create only public repositories. + """ + PUBLIC + + """ + Members will be able to create only private repositories. + """ + PRIVATE + + """ + Members will not be able to create public or private repositories. + """ + DISABLED +} + +""" +The possible values for the members can make purchases setting. +""" +enum EnterpriseMembersCanMakePurchasesSettingValue { + """ + The setting is enabled for organizations in the enterprise. + """ + ENABLED + + """ + The setting is disabled for organizations in the enterprise. + """ + DISABLED +} + +""" +The possible values we have for filtering Platform::Objects::User#enterprises. +""" +enum EnterpriseMembershipType { + """ + Returns all enterprises in which the user is a member, admin, or billing manager. + """ + ALL + + """ + Returns all enterprises in which the user is an admin. + """ + ADMIN + + """ + Returns all enterprises in which the user is a billing manager. + """ + BILLING_MANAGER + + """ + Returns all enterprises in which the user is a member of an org that is owned by the enterprise. + """ + ORG_MEMBERSHIP +} + +""" +Ordering options for enterprises. +""" +input EnterpriseOrder { + """ + The field to order enterprises by. + """ + field: EnterpriseOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which enterprise connections can be ordered. +""" +enum EnterpriseOrderField { + """ + Order enterprises by name + """ + NAME +} + +""" +The connection type for Organization. +""" +type EnterpriseOrganizationMembershipConnection { + """ + A list of edges. + """ + edges: [EnterpriseOrganizationMembershipEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An enterprise organization that a user is a member of. +""" +type EnterpriseOrganizationMembershipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization + + """ + The role of the user in the enterprise membership. + """ + role: EnterpriseUserAccountMembershipRole! +} + +""" +The connection type for User. +""" +type EnterpriseOutsideCollaboratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseOutsideCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A User who is an outside collaborator of an enterprise through one or more organizations. +""" +type EnterpriseOutsideCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The enterprise organization repositories this user is a member of. + """ + repositories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for repositories." orderBy: RepositoryOrder = { + field: NAME + direction: ASC + } + ): EnterpriseRepositoryInfoConnection! +} + +""" +Enterprise information visible to enterprise owners or enterprise owners' personal access tokens (classic) with read:enterprise or admin:enterprise scope. +""" +type EnterpriseOwnerInfo { + """ + A list of all of the administrators for this enterprise. + """ + admins("Only return members within the organizations with these logins" organizationLogins: [String!], "The search string to look for." query: String, "The role to filter by." role: EnterpriseAdministratorRole, "Ordering options for administrators returned from the connection." orderBy: EnterpriseMemberOrder = { + field: LOGIN + direction: ASC + } + , "Only return administrators with this two-factor authentication status." hasTwoFactorEnabled: Boolean = null, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseAdministratorConnection! + + """ + A list of users in the enterprise who currently have two-factor authentication disabled. + """ + affiliatedUsersWithTwoFactorDisabled("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + Whether or not affiliated users with two-factor authentication disabled exist in the enterprise. + """ + affiliatedUsersWithTwoFactorDisabledExist: Boolean! + + """ + The setting value for whether private repository forking is enabled for repositories in organizations in this enterprise. + """ + allowPrivateRepositoryForkingSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided private repository forking setting value. + """ + allowPrivateRepositoryForkingSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The value for the allow private repository forking policy on the enterprise. + """ + allowPrivateRepositoryForkingSettingPolicyValue: EnterpriseAllowPrivateRepositoryForkingPolicyValue + + """ + The setting value for base repository permissions for organizations in this enterprise. + """ + defaultRepositoryPermissionSetting: EnterpriseDefaultRepositoryPermissionSettingValue! + + """ + A list of enterprise organizations configured with the provided base repository permission. + """ + defaultRepositoryPermissionSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The permission to find organizations for." value: DefaultRepositoryPermissionField!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + A list of domains owned by the enterprise. Visible to enterprise owners or enterprise owners' personal access tokens (classic) with admin:enterprise scope. + """ + domains("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter whether or not the domain is verified." isVerified: Boolean = null, "Filter whether or not the domain is approved." isApproved: Boolean = null, "Ordering options for verifiable domains returned." orderBy: VerifiableDomainOrder = { + field: DOMAIN + direction: ASC + } + ): VerifiableDomainConnection! + + """ + Enterprise Server installations owned by the enterprise. + """ + enterpriseServerInstallations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Whether or not to only return installations discovered via GitHub Connect." connectedOnly: Boolean = false, "Ordering options for Enterprise Server installations returned." orderBy: EnterpriseServerInstallationOrder = { + field: HOST_NAME + direction: ASC + } + ): EnterpriseServerInstallationConnection! + + """ + A list of failed invitations in the enterprise. + """ + failedInvitations("The search string to look for." query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseFailedInvitationConnection! + + """ + The setting value for whether the enterprise has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the enterprise. Visible to enterprise owners or enterprise owners' personal access tokens (classic) with admin:enterprise scope. + """ + ipAllowListEntries("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for IP allow list entries returned." orderBy: IpAllowListEntryOrder = { + field: ALLOW_LIST_VALUE + direction: ASC + } + ): IpAllowListEntryConnection! + + """ + The setting value for whether the enterprise has IP allow list configuration for installed GitHub Apps enabled. + """ + ipAllowListForInstalledAppsEnabledSetting: IpAllowListForInstalledAppsEnabledSettingValue! + + """ + Whether or not the base repository permission is currently being updated. + """ + isUpdatingDefaultRepositoryPermission: Boolean! + + """ + Whether the two-factor authentication requirement is currently being enforced. + """ + isUpdatingTwoFactorRequirement: Boolean! + + """ + The setting value for whether organization members with admin permissions on a repository can change repository visibility. + """ + membersCanChangeRepositoryVisibilitySetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided can change repository visibility setting value. + """ + membersCanChangeRepositoryVisibilitySettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can create internal repositories. + """ + membersCanCreateInternalRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create private repositories. + """ + membersCanCreatePrivateRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create public repositories. + """ + membersCanCreatePublicRepositoriesSetting: Boolean + + """ + The setting value for whether members of organizations in the enterprise can create repositories. + """ + membersCanCreateRepositoriesSetting: EnterpriseMembersCanCreateRepositoriesSettingValue + + """ + A list of enterprise organizations configured with the provided repository creation setting value. + """ + membersCanCreateRepositoriesSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting to find organizations for." value: OrganizationMembersCanCreateRepositoriesSettingValue!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete issues. + """ + membersCanDeleteIssuesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete issues setting value. + """ + membersCanDeleteIssuesSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether members with admin permissions for repositories can delete or transfer repositories. + """ + membersCanDeleteRepositoriesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can delete repositories setting value. + """ + membersCanDeleteRepositoriesSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether members of organizations in the enterprise can invite outside collaborators. + """ + membersCanInviteCollaboratorsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can invite collaborators setting value. + """ + membersCanInviteCollaboratorsSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + Indicates whether members of this enterprise's organizations can purchase additional services for those organizations. + """ + membersCanMakePurchasesSetting: EnterpriseMembersCanMakePurchasesSettingValue! + + """ + The setting value for whether members with admin permissions for repositories can update protected branches. + """ + membersCanUpdateProtectedBranchesSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can update protected branches setting value. + """ + membersCanUpdateProtectedBranchesSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether members can view dependency insights. + """ + membersCanViewDependencyInsightsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided members can view dependency insights setting value. + """ + membersCanViewDependencyInsightsSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + Indicates if email notification delivery for this enterprise is restricted to verified or approved domains. + """ + notificationDeliveryRestrictionEnabledSetting: NotificationRestrictionSettingValue! + + """ + The OIDC Identity Provider for the enterprise. + """ + oidcProvider: OIDCProvider + + """ + The setting value for whether organization projects are enabled for organizations in this enterprise. + """ + organizationProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided organization projects setting value. + """ + organizationProjectsSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + A list of outside collaborators across the repositories in the enterprise. + """ + outsideCollaborators("The login of one specific outside collaborator." login: String, "The search string to look for." query: String, "Ordering options for outside collaborators returned from the connection." orderBy: EnterpriseMemberOrder = { + field: LOGIN + direction: ASC + } + , "Only return outside collaborators on repositories with this visibility." visibility: RepositoryVisibility, "Only return outside collaborators with this two-factor authentication status." hasTwoFactorEnabled: Boolean = null, "Only return outside collaborators within the organizations with these logins" organizationLogins: [String!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseOutsideCollaboratorConnection! + + """ + A list of pending administrator invitations for the enterprise. + """ + pendingAdminInvitations("The search string to look for." query: String, "Ordering options for pending enterprise administrator invitations returned from the connection." orderBy: EnterpriseAdministratorInvitationOrder = { + field: CREATED_AT + direction: DESC + } + , "The role to filter by." role: EnterpriseAdministratorRole, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseAdministratorInvitationConnection! + + """ + A list of pending collaborator invitations across the repositories in the enterprise. + """ + pendingCollaboratorInvitations("The search string to look for." query: String, "Ordering options for pending repository collaborator invitations returned from the connection." orderBy: RepositoryInvitationOrder = { + field: CREATED_AT + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryInvitationConnection! + + """ + A list of pending member invitations for organizations in the enterprise. + """ + pendingMemberInvitations("The search string to look for." query: String, "Only return invitations within the organizations with these logins" organizationLogins: [String!], "Only return invitations matching this invitation source" invitationSource: OrganizationInvitationSource, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterprisePendingMemberInvitationConnection! + + """ + A list of pending unaffiliated member invitations for the enterprise. + """ + pendingUnaffiliatedMemberInvitations("The search string to look for." query: String, "Ordering options for pending enterprise member invitations returned from the connection." orderBy: EnterpriseMemberInvitationOrder = { + field: CREATED_AT + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseMemberInvitationConnection! + + """ + The setting value for whether repository projects are enabled in this enterprise. + """ + repositoryProjectsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided repository projects setting value. + """ + repositoryProjectsSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The SAML Identity Provider for the enterprise. + """ + samlIdentityProvider: EnterpriseIdentityProvider + + """ + A list of enterprise organizations configured with the SAML single sign-on setting value. + """ + samlIdentityProviderSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: IdentityProviderConfigurationState!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + A list of members with a support entitlement. + """ + supportEntitlements("Ordering options for support entitlement users returned from the connection." orderBy: EnterpriseMemberOrder = { + field: LOGIN + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseMemberConnection! + + """ + The setting value for whether team discussions are enabled for organizations in this enterprise. + """ + teamDiscussionsSetting: EnterpriseEnabledDisabledSettingValue! + + """ + A list of enterprise organizations configured with the provided team discussions setting value. + """ + teamDiscussionsSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! + + """ + The setting value for whether the enterprise requires two-factor authentication for its organizations and users. + """ + twoFactorRequiredSetting: EnterpriseEnabledSettingValue! + + """ + A list of enterprise organizations configured with the two-factor authentication setting value. + """ + twoFactorRequiredSettingOrganizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The setting value to find organizations for." value: Boolean!, "Ordering options for organizations with this setting." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + ): OrganizationConnection! +} + +""" +The connection type for OrganizationInvitation. +""" +type EnterprisePendingMemberInvitationConnection { + """ + A list of edges. + """ + edges: [EnterprisePendingMemberInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the total count of unique users in the connection. + """ + totalUniqueUserCount: Int! +} + +""" +An invitation to be a member in an enterprise organization. +""" +type EnterprisePendingMemberInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +A subset of repository information queryable from an enterprise. +""" +type EnterpriseRepositoryInfo implements Node { + """ + The Node ID of the EnterpriseRepositoryInfo object + """ + id: ID! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + The repository's name. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! +} + +""" +The connection type for EnterpriseRepositoryInfo. +""" +type EnterpriseRepositoryInfoConnection { + """ + A list of edges. + """ + edges: [EnterpriseRepositoryInfoEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseRepositoryInfo] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseRepositoryInfoEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseRepositoryInfo +} + +""" +An Enterprise Server installation. +""" +type EnterpriseServerInstallation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The customer name to which the Enterprise Server installation belongs. + """ + customerName: String! + + """ + The host name of the Enterprise Server installation. + """ + hostName: String! + + """ + The Node ID of the EnterpriseServerInstallation object + """ + id: ID! + + """ + Whether or not the installation is connected to an Enterprise Server installation via GitHub Connect. + """ + isConnected: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + User accounts on this Enterprise Server installation. + """ + userAccounts("Ordering options for Enterprise Server user accounts returned from the connection." orderBy: EnterpriseServerUserAccountOrder = { + field: LOGIN + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseServerUserAccountConnection! + + """ + User accounts uploads for the Enterprise Server installation. + """ + userAccountsUploads("Ordering options for Enterprise Server user accounts uploads returned from the connection." orderBy: EnterpriseServerUserAccountsUploadOrder = { + field: CREATED_AT + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseServerUserAccountsUploadConnection! +} + +""" +The connection type for EnterpriseServerInstallation. +""" +type EnterpriseServerInstallationConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerInstallationEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerInstallation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerInstallationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerInstallation +} + +""" +The connection type for EnterpriseServerInstallation. +""" +type EnterpriseServerInstallationMembershipConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerInstallationMembershipEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerInstallation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An Enterprise Server installation that a user is a member of. +""" +type EnterpriseServerInstallationMembershipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerInstallation + + """ + The role of the user in the enterprise membership. + """ + role: EnterpriseUserAccountMembershipRole! +} + +""" +Ordering options for Enterprise Server installation connections. +""" +input EnterpriseServerInstallationOrder { + """ + The field to order Enterprise Server installations by. + """ + field: EnterpriseServerInstallationOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which Enterprise Server installation connections can be ordered. +""" +enum EnterpriseServerInstallationOrderField { + """ + Order Enterprise Server installations by host name + """ + HOST_NAME + + """ + Order Enterprise Server installations by customer name + """ + CUSTOMER_NAME + + """ + Order Enterprise Server installations by creation time + """ + CREATED_AT +} + +""" +A user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccount implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + User emails belonging to this user account. + """ + emails("Ordering options for Enterprise Server user account emails returned from the connection." orderBy: EnterpriseServerUserAccountEmailOrder = { + field: EMAIL + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseServerUserAccountEmailConnection! + + """ + The Enterprise Server installation on which this user account exists. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + + """ + The Node ID of the EnterpriseServerUserAccount object + """ + id: ID! + + """ + Whether the user account is a site administrator on the Enterprise Server installation. + """ + isSiteAdmin: Boolean! + + """ + The login of the user account on the Enterprise Server installation. + """ + login: String! + + """ + The profile name of the user account on the Enterprise Server installation. + """ + profileName: String + + """ + The date and time when the user account was created on the Enterprise Server installation. + """ + remoteCreatedAt: DateTime! + + """ + The ID of the user account on the Enterprise Server installation. + """ + remoteUserId: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccount. +""" +type EnterpriseServerUserAccountConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccount] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccount +} + +""" +An email belonging to a user account on an Enterprise Server installation. +""" +type EnterpriseServerUserAccountEmail implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address. + """ + email: String! + + """ + The Node ID of the EnterpriseServerUserAccountEmail object + """ + id: ID! + + """ + Indicates whether this is the primary email of the associated user account. + """ + isPrimary: Boolean! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user account to which the email belongs. + """ + userAccount: EnterpriseServerUserAccount! +} + +""" +The connection type for EnterpriseServerUserAccountEmail. +""" +type EnterpriseServerUserAccountEmailConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountEmailEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountEmail] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountEmailEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountEmail +} + +""" +Ordering options for Enterprise Server user account email connections. +""" +input EnterpriseServerUserAccountEmailOrder { + """ + The field to order emails by. + """ + field: EnterpriseServerUserAccountEmailOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which Enterprise Server user account email connections can be ordered. +""" +enum EnterpriseServerUserAccountEmailOrderField { + """ + Order emails by email + """ + EMAIL +} + +""" +Ordering options for Enterprise Server user account connections. +""" +input EnterpriseServerUserAccountOrder { + """ + The field to order user accounts by. + """ + field: EnterpriseServerUserAccountOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which Enterprise Server user account connections can be ordered. +""" +enum EnterpriseServerUserAccountOrderField { + """ + Order user accounts by login + """ + LOGIN + + """ + Order user accounts by creation time on the Enterprise Server installation + """ + REMOTE_CREATED_AT +} + +""" +A user accounts upload from an Enterprise Server installation. +""" +type EnterpriseServerUserAccountsUpload implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise to which this upload belongs. + """ + enterprise: Enterprise! + + """ + The Enterprise Server installation for which this upload was generated. + """ + enterpriseServerInstallation: EnterpriseServerInstallation! + + """ + The Node ID of the EnterpriseServerUserAccountsUpload object + """ + id: ID! + + """ + The name of the file uploaded. + """ + name: String! + + """ + The synchronization state of the upload + """ + syncState: EnterpriseServerUserAccountsUploadSyncState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for EnterpriseServerUserAccountsUpload. +""" +type EnterpriseServerUserAccountsUploadConnection { + """ + A list of edges. + """ + edges: [EnterpriseServerUserAccountsUploadEdge] + + """ + A list of nodes. + """ + nodes: [EnterpriseServerUserAccountsUpload] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnterpriseServerUserAccountsUploadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: EnterpriseServerUserAccountsUpload +} + +""" +Ordering options for Enterprise Server user accounts upload connections. +""" +input EnterpriseServerUserAccountsUploadOrder { + """ + The field to order user accounts uploads by. + """ + field: EnterpriseServerUserAccountsUploadOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which Enterprise Server user accounts upload connections can be ordered. +""" +enum EnterpriseServerUserAccountsUploadOrderField { + """ + Order user accounts uploads by creation time + """ + CREATED_AT +} + +""" +Synchronization state of the Enterprise Server user accounts upload +""" +enum EnterpriseServerUserAccountsUploadSyncState { + """ + The synchronization of the upload is pending. + """ + PENDING + + """ + The synchronization of the upload succeeded. + """ + SUCCESS + + """ + The synchronization of the upload failed. + """ + FAILURE +} + +""" +An account for a user who is an admin of an enterprise or a member of an enterprise through one or more organizations. +""" +type EnterpriseUserAccount implements Actor & Node { + """ + A URL pointing to the enterprise user account's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The enterprise in which this user account exists. + """ + enterprise: Enterprise! + + """ + A list of Enterprise Server installations this user is a member of. + """ + enterpriseInstallations("The search string to look for." query: String, "Ordering options for installations returned from the connection." orderBy: EnterpriseServerInstallationOrder = { + field: HOST_NAME + direction: ASC + } + , "The role of the user in the installation." role: EnterpriseUserAccountMembershipRole, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseServerInstallationMembershipConnection! + + """ + The Node ID of the EnterpriseUserAccount object + """ + id: ID! + + """ + An identifier for the enterprise user account, a login or email address + """ + login: String! + + """ + The name of the enterprise user account + """ + name: String + + """ + A list of enterprise organizations this user is a member of. + """ + organizations("The search string to look for." query: String, "Ordering options for organizations returned from the connection." orderBy: OrganizationOrder = { + field: LOGIN + direction: ASC + } + , "The role of the user in the enterprise organization." role: EnterpriseUserAccountMembershipRole, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): EnterpriseOrganizationMembershipConnection! + + """ + The HTTP path for this user. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user. + """ + url: URI! + + """ + The user within the enterprise. + """ + user: User +} + +""" +The possible roles for enterprise membership. +""" +enum EnterpriseUserAccountMembershipRole { + """ + The user is a member of an organization in the enterprise. + """ + MEMBER + + """ + The user is an owner of an organization in the enterprise. + """ + OWNER + + """ + The user is not an owner of the enterprise, and not a member or owner of any organizations in the enterprise; only for EMU-enabled enterprises. + """ + UNAFFILIATED +} + +""" +The possible GitHub Enterprise deployments where this user can exist. +""" +enum EnterpriseUserDeployment { + """ + The user is part of a GitHub Enterprise Cloud deployment. + """ + CLOUD + + """ + The user is part of a GitHub Enterprise Server deployment. + """ + SERVER +} + +""" +An environment. +""" +type Environment implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Environment object + """ + id: ID! + + """ + Indicates whether or not this environment is currently pinned to the repository + """ + isPinned: Boolean + + """ + The latest completed deployment with status success, failure, or error if it exists + """ + latestCompletedDeployment: Deployment + + """ + The name of the environment + """ + name: String! + + """ + The position of the environment if it is pinned, null if it is not pinned + """ + pinnedPosition: Int + + """ + The protection rules defined for this environment + """ + protectionRules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentProtectionRuleConnection! +} + +""" +The connection type for Environment. +""" +type EnvironmentConnection { + """ + A list of edges. + """ + edges: [EnvironmentEdge] + + """ + A list of nodes. + """ + nodes: [Environment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type EnvironmentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Environment +} + +""" +Properties by which environments connections can be ordered +""" +enum EnvironmentOrderField { + """ + Order environments by name. + """ + NAME +} + +""" +Properties by which environments connections can be ordered +""" +enum EnvironmentPinnedFilterField { + """ + All environments will be returned. + """ + ALL + + """ + Only pinned environment will be returned + """ + ONLY + + """ + Environments exclude pinned will be returned + """ + NONE +} + +""" +Ordering options for environments +""" +input Environments { + """ + The field to order environments by. + """ + field: EnvironmentOrderField! + + """ + The direction in which to order environments by the specified field. + """ + direction: OrderDirection! +} + +""" +An external identity provisioned by SAML SSO or SCIM. If SAML is configured on the organization, the external identity is visible to (1) organization owners, (2) organization owners' personal access tokens (classic) with read:org or admin:org scope, (3) GitHub App with an installation token with read or write access to members. If SAML is configured on the enterprise, the external identity is visible to (1) enterprise owners, (2) enterprise owners' personal access tokens (classic) with read:enterprise or admin:enterprise scope. +""" +type ExternalIdentity implements Node { + """ + The GUID for this identity + """ + guid: String! + + """ + The Node ID of the ExternalIdentity object + """ + id: ID! + + """ + Organization invitation for this SCIM-provisioned external identity + """ + organizationInvitation: OrganizationInvitation + + """ + SAML Identity attributes + """ + samlIdentity: ExternalIdentitySamlAttributes + + """ + SCIM Identity attributes + """ + scimIdentity: ExternalIdentityScimAttributes + + """ + User linked to this external identity. Will be NULL if this identity has not been claimed by an organization member. + """ + user: User +} + +""" +An attribute for the External Identity attributes collection +""" +type ExternalIdentityAttribute { + """ + The attribute metadata as JSON + """ + metadata: String + + """ + The attribute name + """ + name: String! + + """ + The attribute value + """ + value: String! +} + +""" +The connection type for ExternalIdentity. +""" +type ExternalIdentityConnection { + """ + A list of edges. + """ + edges: [ExternalIdentityEdge] + + """ + A list of nodes. + """ + nodes: [ExternalIdentity] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ExternalIdentityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ExternalIdentity +} + +""" +SAML attributes for the External Identity +""" +type ExternalIdentitySamlAttributes { + """ + SAML Identity attributes + """ + attributes: [ExternalIdentityAttribute!]! + + """ + The emails associated with the SAML identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SAML identity + """ + familyName: String + + """ + Given name of the SAML identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The NameID of the SAML identity + """ + nameId: String + + """ + The userName of the SAML identity + """ + username: String +} + +""" +SCIM attributes for the External Identity +""" +type ExternalIdentityScimAttributes { + """ + The emails associated with the SCIM identity + """ + emails: [UserEmailMetadata!] + + """ + Family name of the SCIM identity + """ + familyName: String + + """ + Given name of the SCIM identity + """ + givenName: String + + """ + The groups linked to this identity in IDP + """ + groups: [String!] + + """ + The userName of the SCIM identity + """ + username: String +} + +""" +A command to add a file at the given path with the given contents as part of a commit. Any existing file at that that path will be replaced. +""" +input FileAddition { + """ + The path in the repository where the file will be located + """ + path: String! + + """ + The base64 encoded contents of the file + """ + contents: Base64String! +} + +""" +A description of a set of changes to a file tree to be made as part of +a git commit, modeled as zero or more file `additions` and zero or more +file `deletions`. + +Both fields are optional; omitting both will produce a commit with no +file changes. + +`deletions` and `additions` describe changes to files identified +by their path in the git tree using unix-style path separators, i.e. +`/`. The root of a git tree is an empty string, so paths are not +slash-prefixed. + +`path` values must be unique across all `additions` and `deletions` +provided. Any duplication will result in a validation error. + +### Encoding + +File contents must be provided in full for each `FileAddition`. + +The `contents` of a `FileAddition` must be encoded using RFC 4648 +compliant base64, i.e. correct padding is required and no characters +outside the standard alphabet may be used. Invalid base64 +encoding will be rejected with a validation error. + +The encoded contents may be binary. + +For text files, no assumptions are made about the character encoding of +the file contents (after base64 decoding). No charset transcoding or +line-ending normalization will be performed; it is the client's +responsibility to manage the character encoding of files they provide. +However, for maximum compatibility we recommend using UTF-8 encoding +and ensuring that all files in a repository use a consistent +line-ending convention (`\n` or `\r\n`), and that all files end +with a newline. + +### Modeling file changes + +Each of the the five types of conceptual changes that can be made in a +git commit can be described using the `FileChanges` type as follows: + +1. New file addition: create file `hello world\n` at path `docs/README.txt`: + + { + "additions" [ + { + "path": "docs/README.txt", + "contents": base64encode("hello world\n") + } + ] + } + +2. Existing file modification: change existing `docs/README.txt` to have new + content `new content here\n`: + + { + "additions" [ + { + "path": "docs/README.txt", + "contents": base64encode("new content here\n") + } + ] + } + +3. Existing file deletion: remove existing file `docs/README.txt`. + Note that the path is required to exist -- specifying a + path that does not exist on the given branch will abort the + commit and return an error. + + { + "deletions" [ + { + "path": "docs/README.txt" + } + ] + } + + +4. File rename with no changes: rename `docs/README.txt` with + previous content `hello world\n` to the same content at + `newdocs/README.txt`: + + { + "deletions" [ + { + "path": "docs/README.txt", + } + ], + "additions" [ + { + "path": "newdocs/README.txt", + "contents": base64encode("hello world\n") + } + ] + } + + +5. File rename with changes: rename `docs/README.txt` with + previous content `hello world\n` to a file at path + `newdocs/README.txt` with content `new contents\n`: + + { + "deletions" [ + { + "path": "docs/README.txt", + } + ], + "additions" [ + { + "path": "newdocs/README.txt", + "contents": base64encode("new contents\n") + } + ] + } +""" +input FileChanges { + """ + Files to delete. + """ + deletions: [FileDeletion!] = [] + + """ + File to add or change. + """ + additions: [FileAddition!] = [] +} + +""" +A command to delete the file at the given path as part of a commit. +""" +input FileDeletion { + """ + The path to delete + """ + path: String! +} + +""" +Prevent commits that include files with specified file extensions from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +type FileExtensionRestrictionParameters { + """ + The file extensions that are restricted from being pushed to the commit graph. + """ + restrictedFileExtensions: [String!]! +} + +""" +Prevent commits that include files with specified file extensions from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +input FileExtensionRestrictionParametersInput { + """ + The file extensions that are restricted from being pushed to the commit graph. + """ + restrictedFileExtensions: [String!]! +} + +""" +Prevent commits that include changes in specified file paths from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +type FilePathRestrictionParameters { + """ + The file paths that are restricted from being pushed to the commit graph. + """ + restrictedFilePaths: [String!]! +} + +""" +Prevent commits that include changes in specified file paths from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +input FilePathRestrictionParametersInput { + """ + The file paths that are restricted from being pushed to the commit graph. + """ + restrictedFilePaths: [String!]! +} + +""" +The possible viewed states of a file . +""" +enum FileViewedState { + """ + The file has new changes since last viewed. + """ + DISMISSED + + """ + The file has been marked as viewed. + """ + VIEWED + + """ + The file has not been marked as viewed. + """ + UNVIEWED +} + +""" +Autogenerated input type of FollowOrganization +""" +input FollowOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the organization to follow. + """ + organizationId: ID! +} + +""" +Autogenerated return type of FollowOrganization. +""" +type FollowOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that was followed. + """ + organization: Organization +} + +""" +Autogenerated input type of FollowUser +""" +input FollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to follow. + """ + userId: ID! +} + +""" +Autogenerated return type of FollowUser. +""" +type FollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was followed. + """ + user: User +} + +""" +The connection type for User. +""" +type FollowerConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The connection type for User. +""" +type FollowingConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A funding platform link for a repository. +""" +type FundingLink { + """ + The funding platform this link is for. + """ + platform: FundingPlatform! + + """ + The configured URL for this funding link. + """ + url: URI! +} + +""" +The possible funding platforms for repository funding links. +""" +enum FundingPlatform { + """ + GitHub funding platform. + """ + GITHUB + + """ + Patreon funding platform. + """ + PATREON + + """ + Open Collective funding platform. + """ + OPEN_COLLECTIVE + + """ + Ko-fi funding platform. + """ + KO_FI + + """ + Tidelift funding platform. + """ + TIDELIFT + + """ + Community Bridge funding platform. + """ + COMMUNITY_BRIDGE + + """ + Liberapay funding platform. + """ + LIBERAPAY + + """ + IssueHunt funding platform. + """ + ISSUEHUNT + + """ + LFX Crowdfunding funding platform. + """ + LFX_CROWDFUNDING + + """ + Polar funding platform. + """ + POLAR + + """ + Buy Me a Coffee funding platform. + """ + BUY_ME_A_COFFEE + + """ + Custom funding platform. + """ + CUSTOM +} + +""" +A generic hovercard context with a message and icon +""" +type GenericHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +A Gist. +""" +type Gist implements Node & Starrable & UniformResourceLocatable { + """ + A list of comments associated with the gist + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): GistCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The gist description. + """ + description: String + + """ + The files in this gist. + """ + files("The maximum number of files to return." limit: Int = 10, "The oid of the files to return" oid: GitObjectID): [GistFile] + + """ + A list of forks associated with the gist + """ + forks("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for gists returned from the connection" orderBy: GistOrder): GistConnection! + + """ + The Node ID of the Gist object + """ + id: ID! + + """ + Identifies if the gist is a fork. + """ + isFork: Boolean! + + """ + Whether the gist is public or not. + """ + isPublic: Boolean! + + """ + The gist name. + """ + name: String! + + """ + The gist owner. + """ + owner: RepositoryOwner + + """ + Identifies when the gist was last pushed to. + """ + pushedAt: DateTime + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: StarOrder): StargazerConnection! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this Gist. + """ + url: URI! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Represents a comment on an Gist. +""" +type GistComment implements Comment & Deletable & Minimizable & Node & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the gist. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the comment body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The associated gist. + """ + gist: Gist! + + """ + The Node ID of the GistComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for GistComment. +""" +type GistCommentConnection { + """ + A list of edges. + """ + edges: [GistCommentEdge] + + """ + A list of nodes. + """ + nodes: [GistComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GistComment +} + +""" +The connection type for Gist. +""" +type GistConnection { + """ + A list of edges. + """ + edges: [GistEdge] + + """ + A list of nodes. + """ + nodes: [Gist] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GistEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Gist +} + +""" +A file in a gist. +""" +type GistFile { + """ + The file name encoded to remove characters that are invalid in URL paths. + """ + encodedName: String + + """ + The gist file encoding. + """ + encoding: String + + """ + The file extension from the file name. + """ + extension: String + + """ + Indicates if this file is an image. + """ + isImage: Boolean! + + """ + Whether the file's contents were truncated. + """ + isTruncated: Boolean! + + """ + The programming language this file is written in. + """ + language: Language + + """ + The gist file name. + """ + name: String + + """ + The gist file size in bytes. + """ + size: Int + + """ + UTF8 text data or null if the file is binary + """ + text("Optionally truncate the returned file to this length." truncate: Int): String +} + +""" +Ordering options for gist connections +""" +input GistOrder { + """ + The field to order repositories by. + """ + field: GistOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which gist connections can be ordered. +""" +enum GistOrderField { + """ + Order gists by creation time + """ + CREATED_AT + + """ + Order gists by update time + """ + UPDATED_AT + + """ + Order gists by push time + """ + PUSHED_AT +} + +""" +The privacy of a Gist +""" +enum GistPrivacy { + """ + Public + """ + PUBLIC + + """ + Secret + """ + SECRET + + """ + Gists that are public and secret + """ + ALL +} + +""" +Represents an actor in a Git commit (ie. an author or committer). +""" +type GitActor { + """ + A URL pointing to the author's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The timestamp of the Git action (authoring or committing). + """ + date: GitTimestamp + + """ + The email in the Git commit. + """ + email: String + + """ + The name in the Git commit. + """ + name: String + + """ + The GitHub user corresponding to the email field. Null if no such user exists. + """ + user: User +} + +""" +The connection type for GitActor. +""" +type GitActorConnection { + """ + A list of edges. + """ + edges: [GitActorEdge] + + """ + A list of nodes. + """ + nodes: [GitActor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type GitActorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: GitActor +} + +""" +Represents information about the GitHub instance. +""" +type GitHubMetadata { + """ + Returns a String that's a SHA of `github-services` + """ + gitHubServicesSha: GitObjectID! + + """ + IP addresses that users connect to for git operations + """ + gitIpAddresses: [String!] + + """ + IP addresses that GitHub Enterprise Importer uses for outbound connections + """ + githubEnterpriseImporterIpAddresses: [String!] + + """ + IP addresses that service hooks are sent from + """ + hookIpAddresses: [String!] + + """ + IP addresses that the importer connects from + """ + importerIpAddresses: [String!] + + """ + Whether or not users are verified + """ + isPasswordAuthenticationVerifiable: Boolean! + + """ + IP addresses for GitHub Pages' A records + """ + pagesIpAddresses: [String!] +} + +""" +Represents a Git object. +""" +interface GitObject { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The Node ID of the GitObject object + """ + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +A Git object ID. +""" +scalar GitObjectID + +""" +A fully qualified reference name (e.g. `refs/heads/master`). +""" +scalar GitRefname + +""" +Git SSH string +""" +scalar GitSSHRemote + +""" +Information about a signature (GPG or S/MIME) on a Commit or Tag. +""" +interface GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +The state of a Git signature. +""" +enum GitSignatureState { + """ + Valid signature and verified by GitHub + """ + VALID + + """ + Invalid signature + """ + INVALID + + """ + Malformed signature + """ + MALFORMED_SIG + + """ + Key used for signing not known to GitHub + """ + UNKNOWN_KEY + + """ + Invalid email used for signing + """ + BAD_EMAIL + + """ + Email used for signing unverified on GitHub + """ + UNVERIFIED_EMAIL + + """ + Email used for signing not known to GitHub + """ + NO_USER + + """ + Unknown signature type + """ + UNKNOWN_SIG_TYPE + + """ + Unsigned + """ + UNSIGNED + + """ + Internal error - the GPG verification service is unavailable at the moment + """ + GPGVERIFY_UNAVAILABLE + + """ + Internal error - the GPG verification service misbehaved + """ + GPGVERIFY_ERROR + + """ + The usage flags for the key that signed this don't allow signing + """ + NOT_SIGNING_KEY + + """ + Signing key expired + """ + EXPIRED_KEY + + """ + Valid signature, pending certificate revocation checking + """ + OCSP_PENDING + + """ + Valid signature, though certificate revocation check failed + """ + OCSP_ERROR + + """ + The signing certificate or its chain could not be verified + """ + BAD_CERT + + """ + One or more certificates in chain has been revoked + """ + OCSP_REVOKED +} + +""" +An ISO-8601 encoded date string. Unlike the DateTime type, GitTimestamp is not converted in UTC. +""" +scalar GitTimestamp + +""" +Represents a GPG signature on a Commit or Tag. +""" +type GpgSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Hex-encoded ID of the key that signed this object. + """ + keyId: String + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Autogenerated input type of GrantEnterpriseOrganizationsMigratorRole +""" +input GrantEnterpriseOrganizationsMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which all organizations managed by it will be granted the migrator role. + """ + enterpriseId: ID! + + """ + The login of the user to grant the migrator role + """ + login: String! +} + +""" +Autogenerated return type of GrantEnterpriseOrganizationsMigratorRole. +""" +type GrantEnterpriseOrganizationsMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organizations that had the migrator role applied to for the given user. + """ + organizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationConnection +} + +""" +Autogenerated input type of GrantMigratorRole +""" +input GrantMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that the user/team belongs to. + """ + organizationId: ID! + + """ + The user login or Team slug to grant the migrator role. + """ + actor: String! + + """ + Specifies the type of the actor, can be either USER or TEAM. + """ + actorType: ActorType! +} + +""" +Autogenerated return type of GrantMigratorRole. +""" +type GrantMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +A string containing HTML code. +""" +scalar HTML + +""" +Represents a 'head_ref_deleted' event on a given pull request. +""" +type HeadRefDeletedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the Ref associated with the `head_ref_deleted` event. + """ + headRef: Ref + + """ + Identifies the name of the Ref associated with the `head_ref_deleted` event. + """ + headRefName: String! + + """ + The Node ID of the HeadRefDeletedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Represents a 'head_ref_force_pushed' event on a given pull request. +""" +type HeadRefForcePushedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the after commit SHA for the 'head_ref_force_pushed' event. + """ + afterCommit: Commit + + """ + Identifies the before commit SHA for the 'head_ref_force_pushed' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the HeadRefForcePushedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the fully qualified ref name for the 'head_ref_force_pushed' event. + """ + ref: Ref +} + +""" +Represents a 'head_ref_restored' event on a given pull request. +""" +type HeadRefRestoredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the HeadRefRestoredEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! +} + +""" +Detail needed to display a hovercard for a user +""" +type Hovercard { + """ + Each of the contexts for this hovercard + """ + contexts: [HovercardContext!]! +} + +""" +An individual line of a hovercard +""" +interface HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! +} + +""" +The possible states in which authentication can be configured with an identity provider. +""" +enum IdentityProviderConfigurationState { + """ + Authentication with an identity provider is configured and enforced. + """ + ENFORCED + + """ + Authentication with an identity provider is configured but not enforced. + """ + CONFIGURED + + """ + Authentication with an identity provider is not configured. + """ + UNCONFIGURED +} + +""" +Autogenerated input type of ImportProject +""" +input ImportProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The name of the Organization or User to create the Project under. + """ + ownerName: String! + + """ + The name of Project. + """ + name: String! + + """ + The description of Project. + """ + body: String + + """ + Whether the Project is public or not. + """ + public: Boolean = false + + """ + A list of columns containing issues and pull requests. + """ + columnImports: [ProjectColumnImport!]! +} + +""" +Autogenerated return type of ImportProject. +""" +type ImportProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new Project! + """ + project: Project +} + +""" +Autogenerated input type of InviteEnterpriseAdmin +""" +input InviteEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which you want to invite an administrator. + """ + enterpriseId: ID! + + """ + The login of a user to invite as an administrator. + """ + invitee: String + + """ + The email of the person to invite as an administrator. + """ + email: String + + """ + The role of the administrator. + """ + role: EnterpriseAdministratorRole +} + +""" +Autogenerated return type of InviteEnterpriseAdmin. +""" +type InviteEnterpriseAdminPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created enterprise administrator invitation. + """ + invitation: EnterpriseAdministratorInvitation +} + +""" +Autogenerated input type of InviteEnterpriseMember +""" +input InviteEnterpriseMemberInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which you want to invite an unaffiliated member. + """ + enterpriseId: ID! + + """ + The login of a user to invite as an unaffiliated member. + """ + invitee: String + + """ + The email of the person to invite as an unaffiliated member. + """ + email: String +} + +""" +Autogenerated return type of InviteEnterpriseMember. +""" +type InviteEnterpriseMemberPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The created enterprise member invitation. + """ + invitation: EnterpriseMemberInvitation +} + +""" +The possible values for the IP allow list enabled setting. +""" +enum IpAllowListEnabledSettingValue { + """ + The setting is enabled for the owner. + """ + ENABLED + + """ + The setting is disabled for the owner. + """ + DISABLED +} + +""" +An IP address or range of addresses that is allowed to access an owner's resources. +""" +type IpAllowListEntry implements Node { + """ + A single IP address or range of IP addresses in CIDR notation. + """ + allowListValue: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the IpAllowListEntry object + """ + id: ID! + + """ + Whether the entry is currently active. + """ + isActive: Boolean! + + """ + The name of the IP allow list entry. + """ + name: String + + """ + The owner of the IP allow list entry. + """ + owner: IpAllowListOwner! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for IpAllowListEntry. +""" +type IpAllowListEntryConnection { + """ + A list of edges. + """ + edges: [IpAllowListEntryEdge] + + """ + A list of nodes. + """ + nodes: [IpAllowListEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IpAllowListEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IpAllowListEntry +} + +""" +Ordering options for IP allow list entry connections. +""" +input IpAllowListEntryOrder { + """ + The field to order IP allow list entries by. + """ + field: IpAllowListEntryOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which IP allow list entry connections can be ordered. +""" +enum IpAllowListEntryOrderField { + """ + Order IP allow list entries by creation time. + """ + CREATED_AT + + """ + Order IP allow list entries by the allow list value. + """ + ALLOW_LIST_VALUE +} + +""" +The possible values for the IP allow list configuration for installed GitHub Apps setting. +""" +enum IpAllowListForInstalledAppsEnabledSettingValue { + """ + The setting is enabled for the owner. + """ + ENABLED + + """ + The setting is disabled for the owner. + """ + DISABLED +} + +""" +Types that can own an IP allow list. +""" +union IpAllowListOwner = App|Enterprise|Organization + +""" +An Issue is a place to discuss ideas, enhancements, tasks, and bugs for a project. +""" +type Issue implements Assignable & Closable & Comment & Deletable & Labelable & Lockable & Node & ProjectV2Owner & Reactable & RepositoryNode & Subscribable & SubscribableThread & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + A list of Users assigned to this object. + """ + assignees("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Identifies the body of the issue. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The http path for this issue body + """ + bodyResourcePath: URI! + + """ + Identifies the body of the issue rendered to text. + """ + bodyText: String! + + """ + The http URL for this issue body + """ + bodyUrl: URI! + + """ + Indicates if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + A list of comments associated with the Issue. + """ + comments("Ordering options for issue comments returned from the connection." orderBy: IssueCommentOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueCommentConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The hovercard information for this issue + """ + hovercard("Whether or not to include notification contexts" includeNotificationContexts: Boolean = true): Hovercard! + + """ + The Node ID of the Issue object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Indicates whether or not this issue is currently pinned to the repository issues list + """ + isPinned: Boolean + + """ + Is this issue read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Branches linked to this issue. + """ + linkedBranches("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LinkedBranchConnection! + + """ + `true` if the object is locked + """ + locked: Boolean! + + """ + Identifies the milestone associated with the issue. + """ + milestone: Milestone + + """ + Identifies the issue number. + """ + number: Int! + + """ + A list of Users that are participating in the Issue conversation. + """ + participants("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + List of project cards associated with this issue. + """ + projectCards("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A list of archived states to filter the cards by" archivedStates: [ProjectCardArchivedState] = [ARCHIVED,NOT_ARCHIVED]): ProjectCardConnection! + + """ + List of project items associated with this issue. + """ + projectItems("Include archived items." includeArchived: Boolean = true, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2ItemConnection! + + """ + Find a project by number. + """ + projectV2("The project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projectsV2("A project to search for under the the owner." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + Identifies the state of the issue. + """ + state: IssueState! + + """ + Identifies the reason for the issue state. + """ + stateReason: IssueStateReason + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timeline("Allows filtering timeline events by a `since` timestamp." since: DateTime, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueTimelineConnection! @deprecated(reason: "`timeline` will be removed Use Issue.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the issue. + """ + timelineItems("Filter timeline items by a `since` timestamp." since: DateTime, "Skips the first _n_ elements in the list." skip: Int, "Filter timeline items by type." itemTypes: [IssueTimelineItemsItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueTimelineItemsConnection! + + """ + Identifies the issue title. + """ + title: String! + + """ + Identifies the issue title rendered to HTML. + """ + titleHTML: String! + + """ + A list of issues that track this issue + """ + trackedInIssues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + A list of issues tracked inside the current issue + """ + trackedIssues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + The number of tracked issues for this issue + """ + trackedIssuesCount("Limit the count to tracked issues with the specified states." states: [TrackedIssueStates]): Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Identifies the viewer's thread subscription form action. + """ + viewerThreadSubscriptionFormAction: ThreadSubscriptionFormAction + + """ + Identifies the viewer's thread subscription status. + """ + viewerThreadSubscriptionStatus: ThreadSubscriptionState +} + +""" +The possible state reasons of a closed issue. +""" +enum IssueClosedStateReason { + """ + An issue that has been closed as completed + """ + COMPLETED + + """ + An issue that has been closed as not planned + """ + NOT_PLANNED +} + +""" +Represents a comment on an Issue. +""" +type IssueComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The Node ID of the IssueComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Identifies the issue associated with the comment. + """ + issue: Issue! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Returns the pull request associated with the comment, if this comment was made on a + pull request. + """ + pullRequest: PullRequest + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this issue comment + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue comment + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for IssueComment. +""" +type IssueCommentConnection { + """ + A list of edges. + """ + edges: [IssueCommentEdge] + + """ + A list of nodes. + """ + nodes: [IssueComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type IssueCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueComment +} + +""" +Ways in which lists of issue comments can be ordered upon return. +""" +input IssueCommentOrder { + """ + The field in which to order issue comments by. + """ + field: IssueCommentOrderField! + + """ + The direction in which to order issue comments by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which issue comment connections can be ordered. +""" +enum IssueCommentOrderField { + """ + Order issue comments by update time + """ + UPDATED_AT +} + +""" +The connection type for Issue. +""" +type IssueConnection { + """ + A list of edges. + """ + edges: [IssueEdge] + + """ + A list of nodes. + """ + nodes: [Issue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates issues opened by a user within one repository. +""" +type IssueContributionsByRepository { + """ + The issue contributions. + """ + contributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedIssueContributionConnection! + + """ + The repository in which the issues were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type IssueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Issue +} + +""" +Ways in which to filter lists of issues. +""" +input IssueFilters { + """ + List issues assigned to given name. Pass in `null` for issues with no assigned user, and `*` for issues assigned to any user. + """ + assignee: String + + """ + List issues created by given name. + """ + createdBy: String + + """ + List issues where the list of label names exist on the issue. + """ + labels: [String!] + + """ + List issues where the given name is mentioned in the issue. + """ + mentioned: String + + """ + List issues by given milestone argument. If an string representation of an integer is passed, it should refer to a milestone by its database ID. Pass in `null` for issues with no milestone, and `*` for issues that are assigned to any milestone. + """ + milestone: String + + """ + List issues by given milestone argument. If an string representation of an integer is passed, it should refer to a milestone by its number field. Pass in `null` for issues with no milestone, and `*` for issues that are assigned to any milestone. + """ + milestoneNumber: String + + """ + List issues that have been updated at or after the given date. + """ + since: DateTime + + """ + List issues filtered by the list of states given. + """ + states: [IssueState!] + + """ + List issues subscribed to by viewer. + """ + viewerSubscribed: Boolean = false +} + +""" +Used for return value of Repository.issueOrPullRequest. +""" +union IssueOrPullRequest = Issue|PullRequest + +""" +Ways in which lists of issues can be ordered upon return. +""" +input IssueOrder { + """ + The field in which to order issues by. + """ + field: IssueOrderField! + + """ + The direction in which to order issues by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which issue connections can be ordered. +""" +enum IssueOrderField { + """ + Order issues by creation time + """ + CREATED_AT + + """ + Order issues by update time + """ + UPDATED_AT + + """ + Order issues by comment count + """ + COMMENTS +} + +""" +The possible states of an issue. +""" +enum IssueState { + """ + An issue that is still open + """ + OPEN + + """ + An issue that has been closed + """ + CLOSED +} + +""" +The possible state reasons of an issue. +""" +enum IssueStateReason { + """ + An issue that has been reopened + """ + REOPENED + + """ + An issue that has been closed as not planned + """ + NOT_PLANNED + + """ + An issue that has been closed as completed + """ + COMPLETED +} + +""" +A repository issue template. +""" +type IssueTemplate { + """ + The template purpose. + """ + about: String + + """ + The suggested assignees. + """ + assignees("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + The suggested issue body. + """ + body: String + + """ + The template filename. + """ + filename: String! + + """ + The suggested issue labels + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection + + """ + The template name. + """ + name: String! + + """ + The suggested issue title. + """ + title: String +} + +""" +The connection type for IssueTimelineItem. +""" +type IssueTimelineConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [IssueTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in an issue timeline +""" +union IssueTimelineItem = AssignedEvent|ClosedEvent|Commit|CrossReferencedEvent|DemilestonedEvent|IssueComment|LabeledEvent|LockedEvent|MilestonedEvent|ReferencedEvent|RenamedTitleEvent|ReopenedEvent|SubscribedEvent|TransferredEvent|UnassignedEvent|UnlabeledEvent|UnlockedEvent|UnsubscribedEvent|UserBlockedEvent + +""" +An edge in a connection. +""" +type IssueTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItem +} + +""" +An item in an issue timeline +""" +union IssueTimelineItems = AddedToProjectEvent|AssignedEvent|ClosedEvent|CommentDeletedEvent|ConnectedEvent|ConvertedNoteToIssueEvent|ConvertedToDiscussionEvent|CrossReferencedEvent|DemilestonedEvent|DisconnectedEvent|IssueComment|LabeledEvent|LockedEvent|MarkedAsDuplicateEvent|MentionedEvent|MilestonedEvent|MovedColumnsInProjectEvent|PinnedEvent|ReferencedEvent|RemovedFromProjectEvent|RenamedTitleEvent|ReopenedEvent|SubscribedEvent|TransferredEvent|UnassignedEvent|UnlabeledEvent|UnlockedEvent|UnmarkedAsDuplicateEvent|UnpinnedEvent|UnsubscribedEvent|UserBlockedEvent + +""" +The connection type for IssueTimelineItems. +""" +type IssueTimelineItemsConnection { + """ + A list of edges. + """ + edges: [IssueTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [IssueTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type IssueTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: IssueTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum IssueTimelineItemsItemType { + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a 'converted_to_discussion' event on a given issue. + """ + CONVERTED_TO_DISCUSSION_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT +} + +""" +Represents a user signing up for a GitHub account. +""" +type JoinedGitHubContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +A label for categorizing Issues, Pull Requests, Milestones, or Discussions with a given Repository. +""" +type Label implements Node { + """ + Identifies the label color. + """ + color: String! + + """ + Identifies the date and time when the label was created. + """ + createdAt: DateTime + + """ + A brief description of this label. + """ + description: String + + """ + The Node ID of the Label object + """ + id: ID! + + """ + Indicates whether or not this is a default label. + """ + isDefault: Boolean! + + """ + A list of issues associated with this label. + """ + issues("Ordering options for issues returned from the connection." orderBy: IssueOrder, "A list of label names to filter the pull requests by." labels: [String!], "A list of states to filter the issues by." states: [IssueState!], "Filtering options for issues returned from the connection." filterBy: IssueFilters, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + Identifies the label name. + """ + name: String! + + """ + A list of pull requests associated with this label. + """ + pullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection! + + """ + The repository associated with this label. + """ + repository: Repository! + + """ + The HTTP path for this label. + """ + resourcePath: URI! + + """ + Identifies the date and time when the label was last updated. + """ + updatedAt: DateTime + + """ + The HTTP URL for this label. + """ + url: URI! +} + +""" +The connection type for Label. +""" +type LabelConnection { + """ + A list of edges. + """ + edges: [LabelEdge] + + """ + A list of nodes. + """ + nodes: [Label] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type LabelEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Label +} + +""" +Ways in which lists of labels can be ordered upon return. +""" +input LabelOrder { + """ + The field in which to order labels by. + """ + field: LabelOrderField! + + """ + The direction in which to order labels by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which label connections can be ordered. +""" +enum LabelOrderField { + """ + Order labels by name + """ + NAME + + """ + Order labels by creation time + """ + CREATED_AT +} + +""" +An object that can have labels assigned to it. +""" +interface Labelable { + """ + A list of labels associated with the object. + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection +} + +""" +Represents a 'labeled' event on a given issue or pull request. +""" +type LabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the LabeledEvent object + """ + id: ID! + + """ + Identifies the label associated with the 'labeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Represents a given language found in repositories. +""" +type Language implements Node { + """ + The color defined for the current language. + """ + color: String + + """ + The Node ID of the Language object + """ + id: ID! + + """ + The name of the current language. + """ + name: String! +} + +""" +A list of languages associated with the parent. +""" +type LanguageConnection { + """ + A list of edges. + """ + edges: [LanguageEdge] + + """ + A list of nodes. + """ + nodes: [Language] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in bytes of files written in that language. + """ + totalSize: Int! +} + +""" +Represents the language of a repository. +""" +type LanguageEdge { + cursor: String! + + node: Language! + + """ + The number of bytes of code written in the language. + """ + size: Int! +} + +""" +Ordering options for language connections. +""" +input LanguageOrder { + """ + The field to order languages by. + """ + field: LanguageOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which language connections can be ordered. +""" +enum LanguageOrderField { + """ + Order languages by the size of all files containing the language + """ + SIZE +} + +""" +A repository's open source license +""" +type License implements Node { + """ + The full text of the license + """ + body: String! + + """ + The conditions set by the license + """ + conditions: [LicenseRule]! + + """ + A human-readable description of the license + """ + description: String + + """ + Whether the license should be featured + """ + featured: Boolean! + + """ + Whether the license should be displayed in license pickers + """ + hidden: Boolean! + + """ + The Node ID of the License object + """ + id: ID! + + """ + Instructions on how to implement the license + """ + implementation: String + + """ + The lowercased SPDX ID of the license + """ + key: String! + + """ + The limitations set by the license + """ + limitations: [LicenseRule]! + + """ + The license full name specified by + """ + name: String! + + """ + Customary short name if applicable (e.g, GPLv3) + """ + nickname: String + + """ + The permissions set by the license + """ + permissions: [LicenseRule]! + + """ + Whether the license is a pseudo-license placeholder (e.g., other, no-license) + """ + pseudoLicense: Boolean! + + """ + Short identifier specified by + """ + spdxId: String + + """ + URL to the license on + """ + url: URI +} + +""" +Describes a License's conditions, permissions, and limitations +""" +type LicenseRule { + """ + A description of the rule + """ + description: String! + + """ + The machine-readable rule key + """ + key: String! + + """ + The human-readable rule label + """ + label: String! +} + +""" +Autogenerated input type of LinkProjectV2ToRepository +""" +input LinkProjectV2ToRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to link to the repository. + """ + projectId: ID! + + """ + The ID of the repository to link to the project. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of LinkProjectV2ToRepository. +""" +type LinkProjectV2ToRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the project is linked to. + """ + repository: Repository +} + +""" +Autogenerated input type of LinkProjectV2ToTeam +""" +input LinkProjectV2ToTeamInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to link to the team. + """ + projectId: ID! + + """ + The ID of the team to link to the project. + """ + teamId: ID! +} + +""" +Autogenerated return type of LinkProjectV2ToTeam. +""" +type LinkProjectV2ToTeamPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team the project is linked to + """ + team: Team +} + +""" +Autogenerated input type of LinkRepositoryToProject +""" +input LinkRepositoryToProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to link to a Repository + """ + projectId: ID! + + """ + The ID of the Repository to link to a Project. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of LinkRepositoryToProject. +""" +type LinkRepositoryToProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +A branch linked to an issue. +""" +type LinkedBranch implements Node { + """ + The Node ID of the LinkedBranch object + """ + id: ID! + + """ + The branch's ref. + """ + ref: Ref +} + +""" +A list of branches linked to an issue. +""" +type LinkedBranchConnection { + """ + A list of edges. + """ + edges: [LinkedBranchEdge] + + """ + A list of nodes. + """ + nodes: [LinkedBranch] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type LinkedBranchEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: LinkedBranch +} + +""" +Autogenerated input type of LockLockable +""" +input LockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the item to be locked. + """ + lockableId: ID! + + """ + A reason for why the item will be locked. + """ + lockReason: LockReason +} + +""" +Autogenerated return type of LockLockable. +""" +type LockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was locked. + """ + lockedRecord: Lockable +} + +""" +The possible reasons that an issue or pull request was locked. +""" +enum LockReason { + """ + The issue or pull request was locked because the conversation was off-topic. + """ + OFF_TOPIC + + """ + The issue or pull request was locked because the conversation was too heated. + """ + TOO_HEATED + + """ + The issue or pull request was locked because the conversation was resolved. + """ + RESOLVED + + """ + The issue or pull request was locked because the conversation was spam. + """ + SPAM +} + +""" +An object that can be locked. +""" +interface Lockable { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + `true` if the object is locked + """ + locked: Boolean! +} + +""" +Represents a 'locked' event on a given issue or pull request. +""" +type LockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the LockedEvent object + """ + id: ID! + + """ + Reason that the conversation was locked (optional). + """ + lockReason: LockReason + + """ + Object that was locked. + """ + lockable: Lockable! +} + +""" +A placeholder user for attribution of imported data on GitHub. +""" +type Mannequin implements Actor & Node & UniformResourceLocatable { + """ + A URL pointing to the GitHub App's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The user that has claimed the data attributed to this mannequin. + """ + claimant: User + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The mannequin's email on the source instance. + """ + email: String + + """ + The Node ID of the Mannequin object + """ + id: ID! + + """ + The username of the actor. + """ + login: String! + + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +A list of mannequins. +""" +type MannequinConnection { + """ + A list of edges. + """ + edges: [MannequinEdge] + + """ + A list of nodes. + """ + nodes: [Mannequin] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a mannequin. +""" +type MannequinEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Mannequin +} + +""" +Ordering options for mannequins. +""" +input MannequinOrder { + """ + The field to order mannequins by. + """ + field: MannequinOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which mannequins can be ordered. +""" +enum MannequinOrderField { + """ + Order mannequins alphabetically by their source login. + """ + LOGIN + + """ + Order mannequins why when they were created. + """ + CREATED_AT +} + +""" +Autogenerated input type of MarkDiscussionCommentAsAnswer +""" +input MarkDiscussionCommentAsAnswerInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to mark as an answer. + """ + id: ID! +} + +""" +Autogenerated return type of MarkDiscussionCommentAsAnswer. +""" +type MarkDiscussionCommentAsAnswerPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that includes the chosen comment. + """ + discussion: Discussion +} + +""" +Autogenerated input type of MarkFileAsViewed +""" +input MarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! + + """ + The path of the file to mark as viewed + """ + path: String! +} + +""" +Autogenerated return type of MarkFileAsViewed. +""" +type MarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of MarkNotificationAsDone +""" +input MarkNotificationAsDoneInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The NotificationThread id. + """ + id: ID! +} + +""" +Autogenerated return type of MarkNotificationAsDone. +""" +type MarkNotificationAsDonePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean + + """ + The user that the notification belongs to. + """ + viewer: User +} + +""" +Autogenerated input type of MarkProjectV2AsTemplate +""" +input MarkProjectV2AsTemplateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to mark as a template. + """ + projectId: ID! +} + +""" +Autogenerated return type of MarkProjectV2AsTemplate. +""" +type MarkProjectV2AsTemplatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of MarkPullRequestReadyForReview +""" +input MarkPullRequestReadyForReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be marked as ready for review. + """ + pullRequestId: ID! +} + +""" +Autogenerated return type of MarkPullRequestReadyForReview. +""" +type MarkPullRequestReadyForReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is ready for review. + """ + pullRequest: PullRequest +} + +""" +Represents a 'marked_as_duplicate' event on a given issue or pull request. +""" +type MarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + + """ + The Node ID of the MarkedAsDuplicateEvent object + """ + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +A public description of a Marketplace category. +""" +type MarketplaceCategory implements Node { + """ + The category's description. + """ + description: String + + """ + The technical description of how apps listed in this category work with GitHub. + """ + howItWorks: String + + """ + The Node ID of the MarketplaceCategory object + """ + id: ID! + + """ + The category's name. + """ + name: String! + + """ + How many Marketplace listings have this as their primary category. + """ + primaryListingCount: Int! + + """ + The HTTP path for this Marketplace category. + """ + resourcePath: URI! + + """ + How many Marketplace listings have this as their secondary category. + """ + secondaryListingCount: Int! + + """ + The short name of the category used in its URL. + """ + slug: String! + + """ + The HTTP URL for this Marketplace category. + """ + url: URI! +} + +""" +A listing in the GitHub integration marketplace. +""" +type MarketplaceListing implements Node { + """ + The GitHub App this listing represents. + """ + app: App + + """ + URL to the listing owner's company site. + """ + companyUrl: URI + + """ + The HTTP path for configuring access to the listing's integration or OAuth app + """ + configurationResourcePath: URI! + + """ + The HTTP URL for configuring access to the listing's integration or OAuth app + """ + configurationUrl: URI! + + """ + URL to the listing's documentation. + """ + documentationUrl: URI + + """ + The listing's detailed description. + """ + extendedDescription: String + + """ + The listing's detailed description rendered to HTML. + """ + extendedDescriptionHTML: HTML! + + """ + The listing's introductory description. + """ + fullDescription: String! + + """ + The listing's introductory description rendered to HTML. + """ + fullDescriptionHTML: HTML! + + """ + Does this listing have any plans with a free trial? + """ + hasPublishedFreeTrialPlans: Boolean! + + """ + Does this listing have a terms of service link? + """ + hasTermsOfService: Boolean! + + """ + Whether the creator of the app is a verified org + """ + hasVerifiedOwner: Boolean! + + """ + A technical description of how this app works with GitHub. + """ + howItWorks: String + + """ + The listing's technical description rendered to HTML. + """ + howItWorksHTML: HTML! + + """ + The Node ID of the MarketplaceListing object + """ + id: ID! + + """ + URL to install the product to the viewer's account or organization. + """ + installationUrl: URI + + """ + Whether this listing's app has been installed for the current viewer + """ + installedForViewer: Boolean! + + """ + Whether this listing has been removed from the Marketplace. + """ + isArchived: Boolean! + + """ + Whether this listing is still an editable draft that has not been submitted for review and is not publicly visible in the Marketplace. + """ + isDraft: Boolean! + + """ + Whether the product this listing represents is available as part of a paid plan. + """ + isPaid: Boolean! + + """ + Whether this listing has been approved for display in the Marketplace. + """ + isPublic: Boolean! + + """ + Whether this listing has been rejected by GitHub for display in the Marketplace. + """ + isRejected: Boolean! + + """ + Whether this listing has been approved for unverified display in the Marketplace. + """ + isUnverified: Boolean! + + """ + Whether this draft listing has been submitted for review for approval to be unverified in the Marketplace. + """ + isUnverifiedPending: Boolean! + + """ + Whether this draft listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromDraft: Boolean! + + """ + Whether this unverified listing has been submitted for review from GitHub for approval to be verified in the Marketplace. + """ + isVerificationPendingFromUnverified: Boolean! + + """ + Whether this listing has been approved for verified display in the Marketplace. + """ + isVerified: Boolean! + + """ + The hex color code, without the leading '#', for the logo background. + """ + logoBackgroundColor: String! + + """ + URL for the listing's logo image. + """ + logoUrl("The size in pixels of the resulting square image." size: Int = 400): URI + + """ + The listing's full name. + """ + name: String! + + """ + The listing's very short description without a trailing period or ampersands. + """ + normalizedShortDescription: String! + + """ + URL to the listing's detailed pricing. + """ + pricingUrl: URI + + """ + The category that best describes the listing. + """ + primaryCategory: MarketplaceCategory! + + """ + URL to the listing's privacy policy, may return an empty string for listings that do not require a privacy policy URL. + """ + privacyPolicyUrl: URI! + + """ + The HTTP path for the Marketplace listing. + """ + resourcePath: URI! + + """ + The URLs for the listing's screenshots. + """ + screenshotUrls: [String]! + + """ + An alternate category that describes the listing. + """ + secondaryCategory: MarketplaceCategory + + """ + The listing's very short description. + """ + shortDescription: String! + + """ + The short name of the listing used in its URL. + """ + slug: String! + + """ + URL to the listing's status page. + """ + statusUrl: URI + + """ + An email address for support for this listing's app. + """ + supportEmail: String + + """ + Either a URL or an email address for support for this listing's app, may return an empty string for listings that do not require a support URL. + """ + supportUrl: URI! + + """ + URL to the listing's terms of service. + """ + termsOfServiceUrl: URI + + """ + The HTTP URL for the Marketplace listing. + """ + url: URI! + + """ + Can the current viewer add plans for this Marketplace listing. + """ + viewerCanAddPlans: Boolean! + + """ + Can the current viewer approve this Marketplace listing. + """ + viewerCanApprove: Boolean! + + """ + Can the current viewer delist this Marketplace listing. + """ + viewerCanDelist: Boolean! + + """ + Can the current viewer edit this Marketplace listing. + """ + viewerCanEdit: Boolean! + + """ + Can the current viewer edit the primary and secondary category of this + Marketplace listing. + """ + viewerCanEditCategories: Boolean! + + """ + Can the current viewer edit the plans for this Marketplace listing. + """ + viewerCanEditPlans: Boolean! + + """ + Can the current viewer return this Marketplace listing to draft state + so it becomes editable again. + """ + viewerCanRedraft: Boolean! + + """ + Can the current viewer reject this Marketplace listing by returning it to + an editable draft state or rejecting it entirely. + """ + viewerCanReject: Boolean! + + """ + Can the current viewer request this listing be reviewed for display in + the Marketplace as verified. + """ + viewerCanRequestApproval: Boolean! + + """ + Indicates whether the current user has an active subscription to this Marketplace listing. + """ + viewerHasPurchased: Boolean! + + """ + Indicates if the current user has purchased a subscription to this Marketplace listing + for all of the organizations the user owns. + """ + viewerHasPurchasedForAllOrganizations: Boolean! + + """ + Does the current viewer role allow them to administer this Marketplace listing. + """ + viewerIsListingAdmin: Boolean! +} + +""" +Look up Marketplace Listings +""" +type MarketplaceListingConnection { + """ + A list of edges. + """ + edges: [MarketplaceListingEdge] + + """ + A list of nodes. + """ + nodes: [MarketplaceListing] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MarketplaceListingEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: MarketplaceListing +} + +""" +Prevent commits that include file paths that exceed a specified character limit from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +type MaxFilePathLengthParameters { + """ + The maximum amount of characters allowed in file paths + """ + maxFilePathLength: Int! +} + +""" +Prevent commits that include file paths that exceed a specified character limit from being pushed to the commit graph. NOTE: This rule is in beta and subject to change +""" +input MaxFilePathLengthParametersInput { + """ + The maximum amount of characters allowed in file paths + """ + maxFilePathLength: Int! +} + +""" +Prevent commits that exceed a specified file size limit from being pushed to the commit. NOTE: This rule is in beta and subject to change +""" +type MaxFileSizeParameters { + """ + The maximum file size allowed in megabytes. This limit does not apply to Git Large File Storage (Git LFS). + """ + maxFileSize: Int! +} + +""" +Prevent commits that exceed a specified file size limit from being pushed to the commit. NOTE: This rule is in beta and subject to change +""" +input MaxFileSizeParametersInput { + """ + The maximum file size allowed in megabytes. This limit does not apply to Git Large File Storage (Git LFS). + """ + maxFileSize: Int! +} + +""" +Represents a member feature request notification +""" +type MemberFeatureRequestNotification implements Node { + """ + Represents member feature request body containing entity name and the number of feature requests + """ + body: String! + + """ + The Node ID of the MemberFeatureRequestNotification object + """ + id: ID! + + """ + Represents member feature request notification title + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Entities that have members who can set status messages. +""" +interface MemberStatusable { + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for user statuses returned from the connection." orderBy: UserStatusOrder = { + field: UPDATED_AT + direction: DESC + } + ): UserStatusConnection! +} + +""" +Audit log entry for a members_can_delete_repos.clear event. +""" +type MembersCanDeleteReposClearAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the MembersCanDeleteReposClearAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.disable event. +""" +type MembersCanDeleteReposDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the MembersCanDeleteReposDisableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a members_can_delete_repos.enable event. +""" +type MembersCanDeleteReposEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the MembersCanDeleteReposEnableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Represents a 'mentioned' event on a given issue or pull request. +""" +type MentionedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the MentionedEvent object + """ + id: ID! +} + +""" +Autogenerated input type of MergeBranch +""" +input MergeBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Repository containing the base branch that will be modified. + """ + repositoryId: ID! + + """ + The name of the base branch that the provided head will be merged into. + """ + base: String! + + """ + The head to merge into the base branch. This can be a branch name or a commit GitObjectID. + """ + head: String! + + """ + Message to use for the merge commit. If omitted, a default will be used. + """ + commitMessage: String + + """ + The email address to associate with this commit. + """ + authorEmail: String +} + +""" +Autogenerated return type of MergeBranch. +""" +type MergeBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The resulting merge Commit. + """ + mergeCommit: Commit +} + +""" +The possible default commit messages for merges. +""" +enum MergeCommitMessage { + """ + Default to the pull request's title. + """ + PR_TITLE + + """ + Default to the pull request's body. + """ + PR_BODY + + """ + Default to a blank commit message. + """ + BLANK +} + +""" +The possible default commit titles for merges. +""" +enum MergeCommitTitle { + """ + Default to the pull request's title. + """ + PR_TITLE + + """ + Default to the classic title for a merge message (e.g., Merge pull request #123 from branch-name). + """ + MERGE_MESSAGE +} + +""" +Autogenerated input type of MergePullRequest +""" +input MergePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be merged. + """ + pullRequestId: ID! + + """ + Commit headline to use for the merge commit; if omitted, a default message will be used. + """ + commitHeadline: String + + """ + Commit body to use for the merge commit; if omitted, a default message will be used + """ + commitBody: String + + """ + OID that the pull request head ref must match to allow merge; if omitted, no check is performed. + """ + expectedHeadOid: GitObjectID + + """ + The merge method to use. If omitted, defaults to 'MERGE' + """ + mergeMethod: PullRequestMergeMethod = MERGE + + """ + The email address to associate with this merge. + """ + authorEmail: String +} + +""" +Autogenerated return type of MergePullRequest. +""" +type MergePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was merged. + """ + pullRequest: PullRequest +} + +""" +The queue of pull request entries to be merged into a protected branch in a repository. +""" +type MergeQueue implements Node { + """ + The configuration for this merge queue + """ + configuration: MergeQueueConfiguration + + """ + The entries in the queue + """ + entries("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): MergeQueueEntryConnection + + """ + The Node ID of the MergeQueue object + """ + id: ID! + + """ + The estimated time in seconds until a newly added entry would be merged + """ + nextEntryEstimatedTimeToMerge: Int + + """ + The repository this merge queue belongs to + """ + repository: Repository + + """ + The HTTP path for this merge queue + """ + resourcePath: URI! + + """ + The HTTP URL for this merge queue + """ + url: URI! +} + +""" +Configuration for a MergeQueue +""" +type MergeQueueConfiguration { + """ + The amount of time in minutes to wait for a check response before considering it a failure. + """ + checkResponseTimeout: Int + + """ + The maximum number of entries to build at once. + """ + maximumEntriesToBuild: Int + + """ + The maximum number of entries to merge at once. + """ + maximumEntriesToMerge: Int + + """ + The merge method to use for this queue. + """ + mergeMethod: PullRequestMergeMethod + + """ + The strategy to use when merging entries. + """ + mergingStrategy: MergeQueueMergingStrategy + + """ + The minimum number of entries required to merge at once. + """ + minimumEntriesToMerge: Int + + """ + The amount of time in minutes to wait before ignoring the minumum number of entries in the queue requirement and merging a collection of entries + """ + minimumEntriesToMergeWaitTime: Int +} + +""" +Entries in a MergeQueue +""" +type MergeQueueEntry implements Node { + """ + The base commit for this entry + """ + baseCommit: Commit + + """ + The date and time this entry was added to the merge queue + """ + enqueuedAt: DateTime! + + """ + The actor that enqueued this entry + """ + enqueuer: Actor! + + """ + The estimated time in seconds until this entry will be merged + """ + estimatedTimeToMerge: Int + + """ + The head commit for this entry + """ + headCommit: Commit + + """ + The Node ID of the MergeQueueEntry object + """ + id: ID! + + """ + Whether this pull request should jump the queue + """ + jump: Boolean! + + """ + The merge queue that this entry belongs to + """ + mergeQueue: MergeQueue + + """ + The position of this entry in the queue + """ + position: Int! + + """ + The pull request that will be added to a merge group + """ + pullRequest: PullRequest + + """ + Does this pull request need to be deployed on its own + """ + solo: Boolean! + + """ + The state of this entry in the queue + """ + state: MergeQueueEntryState! +} + +""" +The connection type for MergeQueueEntry. +""" +type MergeQueueEntryConnection { + """ + A list of edges. + """ + edges: [MergeQueueEntryEdge] + + """ + A list of nodes. + """ + nodes: [MergeQueueEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MergeQueueEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: MergeQueueEntry +} + +""" +The possible states for a merge queue entry. +""" +enum MergeQueueEntryState { + """ + The entry is currently queued. + """ + QUEUED + + """ + The entry is currently waiting for checks to pass. + """ + AWAITING_CHECKS + + """ + The entry is currently mergeable. + """ + MERGEABLE + + """ + The entry is currently unmergeable. + """ + UNMERGEABLE + + """ + The entry is currently locked. + """ + LOCKED +} + +""" +The possible merging strategies for a merge queue. +""" +enum MergeQueueMergingStrategy { + """ + Entries only allowed to merge if they are passing. + """ + ALLGREEN + + """ + Failing Entires are allowed to merge if they are with a passing entry. + """ + HEADGREEN +} + +""" +Detailed status information about a pull request merge. +""" +enum MergeStateStatus { + """ + The merge commit cannot be cleanly created. + """ + DIRTY + + """ + The state cannot currently be determined. + """ + UNKNOWN + + """ + The merge is blocked. + """ + BLOCKED + + """ + The head ref is out of date. + """ + BEHIND + + """ + The merge is blocked due to the pull request being a draft. + """ + DRAFT @deprecated(reason: "DRAFT state will be removed from this enum and `isDraft` should be used instead Use PullRequest.isDraft instead. Removal on 2021-01-01 UTC.") + + """ + Mergeable with non-passing commit status. + """ + UNSTABLE + + """ + Mergeable with passing commit status and pre-receive hooks. + """ + HAS_HOOKS + + """ + Mergeable and passing commit status. + """ + CLEAN +} + +""" +Whether or not a PullRequest can be merged. +""" +enum MergeableState { + """ + The pull request can be merged. + """ + MERGEABLE + + """ + The pull request cannot be merged due to merge conflicts. + """ + CONFLICTING + + """ + The mergeability of the pull request is still being calculated. + """ + UNKNOWN +} + +""" +Represents a 'merged' event on a given pull request. +""" +type MergedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the `merge` event. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the MergedEvent object + """ + id: ID! + + """ + Identifies the Ref associated with the `merge` event. + """ + mergeRef: Ref + + """ + Identifies the name of the Ref associated with the `merge` event. + """ + mergeRefName: String! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this merged event. + """ + resourcePath: URI! + + """ + The HTTP URL for this merged event. + """ + url: URI! +} + +""" +Represents a GitHub Enterprise Importer (GEI) migration. +""" +interface Migration { + """ + The migration flag to continue on error. + """ + continueOnError: Boolean! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: String + + """ + The reason the migration failed. + """ + failureReason: String + + """ + The Node ID of the Migration object + """ + id: ID! + + """ + The URL for the migration log (expires 1 day after migration completes). + """ + migrationLogUrl: URI + + """ + The migration source. + """ + migrationSource: MigrationSource! + + """ + The target repository name. + """ + repositoryName: String! + + """ + The migration source URL, for example `https://github.com` or `https://monalisa.ghe.com`. + """ + sourceUrl: URI! + + """ + The migration state. + """ + state: MigrationState! + + """ + The number of warnings encountered for this migration. To review the warnings, check the [Migration Log](https://docs.github.com/migrations/using-github-enterprise-importer/completing-your-migration-with-github-enterprise-importer/accessing-your-migration-logs-for-github-enterprise-importer). + """ + warningsCount: Int! +} + +""" +A GitHub Enterprise Importer (GEI) migration source. +""" +type MigrationSource implements Node { + """ + The Node ID of the MigrationSource object + """ + id: ID! + + """ + The migration source name. + """ + name: String! + + """ + The migration source type. + """ + type: MigrationSourceType! + + """ + The migration source URL, for example `https://github.com` or `https://monalisa.ghe.com`. + """ + url: URI! +} + +""" +Represents the different GitHub Enterprise Importer (GEI) migration sources. +""" +enum MigrationSourceType { + """ + An Azure DevOps migration source. + """ + AZURE_DEVOPS + + """ + A Bitbucket Server migration source. + """ + BITBUCKET_SERVER + + """ + A GitHub Migration API source. + """ + GITHUB_ARCHIVE +} + +""" +The GitHub Enterprise Importer (GEI) migration state. +""" +enum MigrationState { + """ + The migration has not started. + """ + NOT_STARTED + + """ + The migration has been queued. + """ + QUEUED + + """ + The migration is in progress. + """ + IN_PROGRESS + + """ + The migration has succeeded. + """ + SUCCEEDED + + """ + The migration has failed. + """ + FAILED + + """ + The migration needs to have its credentials validated. + """ + PENDING_VALIDATION + + """ + The migration has invalid credentials. + """ + FAILED_VALIDATION +} + +""" +Represents a Milestone object on a given repository. +""" +type Milestone implements Closable & Node & UniformResourceLocatable { + """ + Indicates if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the actor who created the milestone. + """ + creator: Actor + + """ + Identifies the description of the milestone. + """ + description: String + + """ + Identifies the due date of the milestone. + """ + dueOn: DateTime + + """ + The Node ID of the Milestone object + """ + id: ID! + + """ + A list of issues associated with the milestone. + """ + issues("Ordering options for issues returned from the connection." orderBy: IssueOrder, "A list of label names to filter the pull requests by." labels: [String!], "A list of states to filter the issues by." states: [IssueState!], "Filtering options for issues returned from the connection." filterBy: IssueFilters, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + Identifies the number of the milestone. + """ + number: Int! + + """ + Identifies the percentage complete for the milestone + """ + progressPercentage: Float! + + """ + A list of pull requests associated with the milestone. + """ + pullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection! + + """ + The repository associated with this milestone. + """ + repository: Repository! + + """ + The HTTP path for this milestone + """ + resourcePath: URI! + + """ + Identifies the state of the milestone. + """ + state: MilestoneState! + + """ + Identifies the title of the milestone. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this milestone + """ + url: URI! + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! +} + +""" +The connection type for Milestone. +""" +type MilestoneConnection { + """ + A list of edges. + """ + edges: [MilestoneEdge] + + """ + A list of nodes. + """ + nodes: [Milestone] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type MilestoneEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Milestone +} + +""" +Types that can be inside a Milestone. +""" +union MilestoneItem = Issue|PullRequest + +""" +Ordering options for milestone connections. +""" +input MilestoneOrder { + """ + The field to order milestones by. + """ + field: MilestoneOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which milestone connections can be ordered. +""" +enum MilestoneOrderField { + """ + Order milestones by when they are due. + """ + DUE_DATE + + """ + Order milestones by when they were created. + """ + CREATED_AT + + """ + Order milestones by when they were last updated. + """ + UPDATED_AT + + """ + Order milestones by their number. + """ + NUMBER +} + +""" +The possible states of a milestone. +""" +enum MilestoneState { + """ + A milestone that is still open. + """ + OPEN + + """ + A milestone that has been closed. + """ + CLOSED +} + +""" +Represents a 'milestoned' event on a given issue or pull request. +""" +type MilestonedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the MilestonedEvent object + """ + id: ID! + + """ + Identifies the milestone title associated with the 'milestoned' event. + """ + milestoneTitle: String! + + """ + Object referenced by event. + """ + subject: MilestoneItem! +} + +""" +Entities that can be minimized. +""" +interface Minimizable { + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! +} + +""" +Autogenerated input type of MinimizeComment +""" +input MinimizeCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + + """ + The classification of comment + """ + classifier: ReportedContentClassifiers! +} + +""" +Autogenerated return type of MinimizeComment. +""" +type MinimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was minimized. + """ + minimizedComment: Minimizable +} + +""" +Autogenerated input type of MoveProjectCard +""" +input MoveProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the card to move. + """ + cardId: ID! + + """ + The id of the column to move it into. + """ + columnId: ID! + + """ + Place the new card after the card with this id. Pass null to place it at the top. + """ + afterCardId: ID +} + +""" +Autogenerated return type of MoveProjectCard. +""" +type MoveProjectCardPayload { + """ + The new edge of the moved card. + """ + cardEdge: ProjectCardEdge + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of MoveProjectColumn +""" +input MoveProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the column to move. + """ + columnId: ID! + + """ + Place the new column after the column with this id. Pass null to place it at the front. + """ + afterColumnId: ID +} + +""" +Autogenerated return type of MoveProjectColumn. +""" +type MoveProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new edge of the moved column. + """ + columnEdge: ProjectColumnEdge +} + +""" +Represents a 'moved_columns_in_project' event on a given issue or pull request. +""" +type MovedColumnsInProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the MovedColumnsInProjectEvent object + """ + id: ID! + + """ + Column name the issue or pull request was moved from. + """ + previousProjectColumnName: String! + + """ + Project referenced by event. + """ + project: Project + + """ + Project card referenced by this project event. + """ + projectCard: ProjectCard + + """ + Column name the issue or pull request was moved to. + """ + projectColumnName: String! +} + +""" +The root query for implementing GraphQL mutations. +""" +type Mutation { + """ + Clear all of a customer's queued migrations + """ + abortQueuedMigrations("Parameters for AbortQueuedMigrations" input: AbortQueuedMigrationsInput!): AbortQueuedMigrationsPayload + + """ + Abort a repository migration queued or in progress. + """ + abortRepositoryMigration("Parameters for AbortRepositoryMigration" input: AbortRepositoryMigrationInput!): AbortRepositoryMigrationPayload + + """ + Accepts a pending invitation for a user to become an administrator of an enterprise. + """ + acceptEnterpriseAdministratorInvitation("Parameters for AcceptEnterpriseAdministratorInvitation" input: AcceptEnterpriseAdministratorInvitationInput!): AcceptEnterpriseAdministratorInvitationPayload + + """ + Accepts a pending invitation for a user to become an unaffiliated member of an enterprise. + """ + acceptEnterpriseMemberInvitation("Parameters for AcceptEnterpriseMemberInvitation" input: AcceptEnterpriseMemberInvitationInput!): AcceptEnterpriseMemberInvitationPayload + + """ + Applies a suggested topic to the repository. + """ + acceptTopicSuggestion("Parameters for AcceptTopicSuggestion" input: AcceptTopicSuggestionInput!): AcceptTopicSuggestionPayload + + """ + Adds assignees to an assignable object. + """ + addAssigneesToAssignable("Parameters for AddAssigneesToAssignable" input: AddAssigneesToAssignableInput!): AddAssigneesToAssignablePayload + + """ + Adds a comment to an Issue or Pull Request. + """ + addComment("Parameters for AddComment" input: AddCommentInput!): AddCommentPayload + + """ + Adds a comment to a Discussion, possibly as a reply to another comment. + """ + addDiscussionComment("Parameters for AddDiscussionComment" input: AddDiscussionCommentInput!): AddDiscussionCommentPayload + + """ + Vote for an option in a discussion poll. + """ + addDiscussionPollVote("Parameters for AddDiscussionPollVote" input: AddDiscussionPollVoteInput!): AddDiscussionPollVotePayload + + """ + Adds enterprise members to an organization within the enterprise. + """ + addEnterpriseOrganizationMember("Parameters for AddEnterpriseOrganizationMember" input: AddEnterpriseOrganizationMemberInput!): AddEnterpriseOrganizationMemberPayload + + """ + Adds a support entitlement to an enterprise member. + """ + addEnterpriseSupportEntitlement("Parameters for AddEnterpriseSupportEntitlement" input: AddEnterpriseSupportEntitlementInput!): AddEnterpriseSupportEntitlementPayload + + """ + Adds labels to a labelable object. + """ + addLabelsToLabelable("Parameters for AddLabelsToLabelable" input: AddLabelsToLabelableInput!): AddLabelsToLabelablePayload + + """ + Adds a card to a ProjectColumn. Either `contentId` or `note` must be provided but **not** both. + """ + addProjectCard("Parameters for AddProjectCard" input: AddProjectCardInput!): AddProjectCardPayload + + """ + Adds a column to a Project. + """ + addProjectColumn("Parameters for AddProjectColumn" input: AddProjectColumnInput!): AddProjectColumnPayload + + """ + Creates a new draft issue and add it to a Project. + """ + addProjectV2DraftIssue("Parameters for AddProjectV2DraftIssue" input: AddProjectV2DraftIssueInput!): AddProjectV2DraftIssuePayload + + """ + Links an existing content instance to a Project. + """ + addProjectV2ItemById("Parameters for AddProjectV2ItemById" input: AddProjectV2ItemByIdInput!): AddProjectV2ItemByIdPayload + + """ + Adds a review to a Pull Request. + """ + addPullRequestReview("Parameters for AddPullRequestReview" input: AddPullRequestReviewInput!): AddPullRequestReviewPayload + + """ + Adds a comment to a review. + """ + addPullRequestReviewComment("Parameters for AddPullRequestReviewComment" input: AddPullRequestReviewCommentInput!): AddPullRequestReviewCommentPayload + + """ + Adds a new thread to a pending Pull Request Review. + """ + addPullRequestReviewThread("Parameters for AddPullRequestReviewThread" input: AddPullRequestReviewThreadInput!): AddPullRequestReviewThreadPayload + + """ + Adds a reply to an existing Pull Request Review Thread. + """ + addPullRequestReviewThreadReply("Parameters for AddPullRequestReviewThreadReply" input: AddPullRequestReviewThreadReplyInput!): AddPullRequestReviewThreadReplyPayload + + """ + Adds a reaction to a subject. + """ + addReaction("Parameters for AddReaction" input: AddReactionInput!): AddReactionPayload + + """ + Adds a star to a Starrable. + """ + addStar("Parameters for AddStar" input: AddStarInput!): AddStarPayload + + """ + Add an upvote to a discussion or discussion comment. + """ + addUpvote("Parameters for AddUpvote" input: AddUpvoteInput!): AddUpvotePayload + + """ + Adds a verifiable domain to an owning account. + """ + addVerifiableDomain("Parameters for AddVerifiableDomain" input: AddVerifiableDomainInput!): AddVerifiableDomainPayload + + """ + Approve all pending deployments under one or more environments + """ + approveDeployments("Parameters for ApproveDeployments" input: ApproveDeploymentsInput!): ApproveDeploymentsPayload + + """ + Approve a verifiable domain for notification delivery. + """ + approveVerifiableDomain("Parameters for ApproveVerifiableDomain" input: ApproveVerifiableDomainInput!): ApproveVerifiableDomainPayload + + """ + Archives a ProjectV2Item + """ + archiveProjectV2Item("Parameters for ArchiveProjectV2Item" input: ArchiveProjectV2ItemInput!): ArchiveProjectV2ItemPayload + + """ + Marks a repository as archived. + """ + archiveRepository("Parameters for ArchiveRepository" input: ArchiveRepositoryInput!): ArchiveRepositoryPayload + + """ + Cancels a pending invitation for an administrator to join an enterprise. + """ + cancelEnterpriseAdminInvitation("Parameters for CancelEnterpriseAdminInvitation" input: CancelEnterpriseAdminInvitationInput!): CancelEnterpriseAdminInvitationPayload + + """ + Cancels a pending invitation for an unaffiliated member to join an enterprise. + """ + cancelEnterpriseMemberInvitation("Parameters for CancelEnterpriseMemberInvitation" input: CancelEnterpriseMemberInvitationInput!): CancelEnterpriseMemberInvitationPayload + + """ + Cancel an active sponsorship. + """ + cancelSponsorship("Parameters for CancelSponsorship" input: CancelSponsorshipInput!): CancelSponsorshipPayload + + """ + Update your status on GitHub. + """ + changeUserStatus("Parameters for ChangeUserStatus" input: ChangeUserStatusInput!): ChangeUserStatusPayload + + """ + Clears all labels from a labelable object. + """ + clearLabelsFromLabelable("Parameters for ClearLabelsFromLabelable" input: ClearLabelsFromLabelableInput!): ClearLabelsFromLabelablePayload + + """ + This mutation clears the value of a field for an item in a Project. Currently only text, number, date, assignees, labels, single-select, iteration and milestone fields are supported. + """ + clearProjectV2ItemFieldValue("Parameters for ClearProjectV2ItemFieldValue" input: ClearProjectV2ItemFieldValueInput!): ClearProjectV2ItemFieldValuePayload + + """ + Creates a new project by cloning configuration from an existing project. + """ + cloneProject("Parameters for CloneProject" input: CloneProjectInput!): CloneProjectPayload + + """ + Create a new repository with the same files and directory structure as a template repository. + """ + cloneTemplateRepository("Parameters for CloneTemplateRepository" input: CloneTemplateRepositoryInput!): CloneTemplateRepositoryPayload + + """ + Close a discussion. + """ + closeDiscussion("Parameters for CloseDiscussion" input: CloseDiscussionInput!): CloseDiscussionPayload + + """ + Close an issue. + """ + closeIssue("Parameters for CloseIssue" input: CloseIssueInput!): CloseIssuePayload + + """ + Close a pull request. + """ + closePullRequest("Parameters for ClosePullRequest" input: ClosePullRequestInput!): ClosePullRequestPayload + + """ + Convert a project note card to one associated with a newly created issue. + """ + convertProjectCardNoteToIssue("Parameters for ConvertProjectCardNoteToIssue" input: ConvertProjectCardNoteToIssueInput!): ConvertProjectCardNoteToIssuePayload + + """ + Converts a projectV2 draft issue item to an issue. + """ + convertProjectV2DraftIssueItemToIssue("Parameters for ConvertProjectV2DraftIssueItemToIssue" input: ConvertProjectV2DraftIssueItemToIssueInput!): ConvertProjectV2DraftIssueItemToIssuePayload + + """ + Converts a pull request to draft + """ + convertPullRequestToDraft("Parameters for ConvertPullRequestToDraft" input: ConvertPullRequestToDraftInput!): ConvertPullRequestToDraftPayload + + """ + Copy a project. + """ + copyProjectV2("Parameters for CopyProjectV2" input: CopyProjectV2Input!): CopyProjectV2Payload + + """ + Invites a user to claim reattributable data + """ + createAttributionInvitation("Parameters for CreateAttributionInvitation" input: CreateAttributionInvitationInput!): CreateAttributionInvitationPayload + + """ + Create a new branch protection rule + """ + createBranchProtectionRule("Parameters for CreateBranchProtectionRule" input: CreateBranchProtectionRuleInput!): CreateBranchProtectionRulePayload + + """ + Create a check run. + """ + createCheckRun("Parameters for CreateCheckRun" input: CreateCheckRunInput!): CreateCheckRunPayload + + """ + Create a check suite + """ + createCheckSuite("Parameters for CreateCheckSuite" input: CreateCheckSuiteInput!): CreateCheckSuitePayload + + """ + Appends a commit to the given branch as the authenticated user. + + This mutation creates a commit whose parent is the HEAD of the provided + branch and also updates that branch to point to the new commit. + It can be thought of as similar to `git commit`. + + ### Locating a Branch + + Commits are appended to a `branch` of type `Ref`. + This must refer to a git branch (i.e. the fully qualified path must + begin with `refs/heads/`, although including this prefix is optional. + + Callers may specify the `branch` to commit to either by its global node + ID or by passing both of `repositoryNameWithOwner` and `refName`. For + more details see the documentation for `CommittableBranch`. + + ### Describing Changes + + `fileChanges` are specified as a `FilesChanges` object describing + `FileAdditions` and `FileDeletions`. + + Please see the documentation for `FileChanges` for more information on + how to use this argument to describe any set of file changes. + + ### Authorship + + Similar to the web commit interface, this mutation does not support + specifying the author or committer of the commit and will not add + support for this in the future. + + A commit created by a successful execution of this mutation will be + authored by the owner of the credential which authenticates the API + request. The committer will be identical to that of commits authored + using the web interface. + + If you need full control over author and committer information, please + use the Git Database REST API instead. + + ### Commit Signing + + Commits made using this mutation are automatically signed by GitHub if + supported and will be marked as verified in the user interface. + """ + createCommitOnBranch("Parameters for CreateCommitOnBranch" input: CreateCommitOnBranchInput!): CreateCommitOnBranchPayload + + """ + Creates a new deployment event. + """ + createDeployment("Parameters for CreateDeployment" input: CreateDeploymentInput!): CreateDeploymentPayload + + """ + Create a deployment status. + """ + createDeploymentStatus("Parameters for CreateDeploymentStatus" input: CreateDeploymentStatusInput!): CreateDeploymentStatusPayload + + """ + Create a discussion. + """ + createDiscussion("Parameters for CreateDiscussion" input: CreateDiscussionInput!): CreateDiscussionPayload + + """ + Creates an organization as part of an enterprise account. A personal access token used to create an organization is implicitly permitted to update the organization it created, if the organization is part of an enterprise that has SAML enabled or uses Enterprise Managed Users. If the organization is not part of such an enterprise, and instead has SAML enabled for it individually, the token will then require SAML authorization to continue working against that organization. + """ + createEnterpriseOrganization("Parameters for CreateEnterpriseOrganization" input: CreateEnterpriseOrganizationInput!): CreateEnterpriseOrganizationPayload + + """ + Creates an environment or simply returns it if already exists. + """ + createEnvironment("Parameters for CreateEnvironment" input: CreateEnvironmentInput!): CreateEnvironmentPayload + + """ + Creates a new IP allow list entry. + """ + createIpAllowListEntry("Parameters for CreateIpAllowListEntry" input: CreateIpAllowListEntryInput!): CreateIpAllowListEntryPayload + + """ + Creates a new issue. + """ + createIssue("Parameters for CreateIssue" input: CreateIssueInput!): CreateIssuePayload + + """ + Creates a new label. + """ + createLabel("Parameters for CreateLabel" input: CreateLabelInput!): CreateLabelPayload + + """ + Create a branch linked to an issue. + """ + createLinkedBranch("Parameters for CreateLinkedBranch" input: CreateLinkedBranchInput!): CreateLinkedBranchPayload + + """ + Creates a GitHub Enterprise Importer (GEI) migration source. + """ + createMigrationSource("Parameters for CreateMigrationSource" input: CreateMigrationSourceInput!): CreateMigrationSourcePayload + + """ + Creates a new project. + """ + createProject("Parameters for CreateProject" input: CreateProjectInput!): CreateProjectPayload + + """ + Creates a new project. + """ + createProjectV2("Parameters for CreateProjectV2" input: CreateProjectV2Input!): CreateProjectV2Payload + + """ + Create a new project field. + """ + createProjectV2Field("Parameters for CreateProjectV2Field" input: CreateProjectV2FieldInput!): CreateProjectV2FieldPayload + + """ + Creates a status update within a Project. + """ + createProjectV2StatusUpdate("Parameters for CreateProjectV2StatusUpdate" input: CreateProjectV2StatusUpdateInput!): CreateProjectV2StatusUpdatePayload + + """ + Create a new pull request + """ + createPullRequest("Parameters for CreatePullRequest" input: CreatePullRequestInput!): CreatePullRequestPayload + + """ + Create a new Git Ref. + """ + createRef("Parameters for CreateRef" input: CreateRefInput!): CreateRefPayload + + """ + Create a new repository. + """ + createRepository("Parameters for CreateRepository" input: CreateRepositoryInput!): CreateRepositoryPayload + + """ + Create a repository ruleset + """ + createRepositoryRuleset("Parameters for CreateRepositoryRuleset" input: CreateRepositoryRulesetInput!): CreateRepositoryRulesetPayload + + """ + Create a GitHub Sponsors profile to allow others to sponsor you or your organization. + """ + createSponsorsListing("Parameters for CreateSponsorsListing" input: CreateSponsorsListingInput!): CreateSponsorsListingPayload + + """ + Create a new payment tier for your GitHub Sponsors profile. + """ + createSponsorsTier("Parameters for CreateSponsorsTier" input: CreateSponsorsTierInput!): CreateSponsorsTierPayload + + """ + Start a new sponsorship of a maintainer in GitHub Sponsors, or reactivate a past sponsorship. + """ + createSponsorship("Parameters for CreateSponsorship" input: CreateSponsorshipInput!): CreateSponsorshipPayload + + """ + Make many sponsorships for different sponsorable users or organizations at once. Can only sponsor those who have a public GitHub Sponsors profile. + """ + createSponsorships("Parameters for CreateSponsorships" input: CreateSponsorshipsInput!): CreateSponsorshipsPayload + + """ + Creates a new team discussion. + """ + createTeamDiscussion("Parameters for CreateTeamDiscussion" input: CreateTeamDiscussionInput!): CreateTeamDiscussionPayload + + """ + Creates a new team discussion comment. + """ + createTeamDiscussionComment("Parameters for CreateTeamDiscussionComment" input: CreateTeamDiscussionCommentInput!): CreateTeamDiscussionCommentPayload + + """ + Creates a new user list. + """ + createUserList("Parameters for CreateUserList" input: CreateUserListInput!): CreateUserListPayload + + """ + Rejects a suggested topic for the repository. + """ + declineTopicSuggestion("Parameters for DeclineTopicSuggestion" input: DeclineTopicSuggestionInput!): DeclineTopicSuggestionPayload + + """ + Delete a branch protection rule + """ + deleteBranchProtectionRule("Parameters for DeleteBranchProtectionRule" input: DeleteBranchProtectionRuleInput!): DeleteBranchProtectionRulePayload + + """ + Deletes a deployment. + """ + deleteDeployment("Parameters for DeleteDeployment" input: DeleteDeploymentInput!): DeleteDeploymentPayload + + """ + Delete a discussion and all of its replies. + """ + deleteDiscussion("Parameters for DeleteDiscussion" input: DeleteDiscussionInput!): DeleteDiscussionPayload + + """ + Delete a discussion comment. If it has replies, wipe it instead. + """ + deleteDiscussionComment("Parameters for DeleteDiscussionComment" input: DeleteDiscussionCommentInput!): DeleteDiscussionCommentPayload + + """ + Deletes an environment + """ + deleteEnvironment("Parameters for DeleteEnvironment" input: DeleteEnvironmentInput!): DeleteEnvironmentPayload + + """ + Deletes an IP allow list entry. + """ + deleteIpAllowListEntry("Parameters for DeleteIpAllowListEntry" input: DeleteIpAllowListEntryInput!): DeleteIpAllowListEntryPayload + + """ + Deletes an Issue object. + """ + deleteIssue("Parameters for DeleteIssue" input: DeleteIssueInput!): DeleteIssuePayload + + """ + Deletes an IssueComment object. + """ + deleteIssueComment("Parameters for DeleteIssueComment" input: DeleteIssueCommentInput!): DeleteIssueCommentPayload + + """ + Deletes a label. + """ + deleteLabel("Parameters for DeleteLabel" input: DeleteLabelInput!): DeleteLabelPayload + + """ + Unlink a branch from an issue. + """ + deleteLinkedBranch("Parameters for DeleteLinkedBranch" input: DeleteLinkedBranchInput!): DeleteLinkedBranchPayload + + """ + Delete a package version. + """ + deletePackageVersion("Parameters for DeletePackageVersion" input: DeletePackageVersionInput!): DeletePackageVersionPayload + + """ + Deletes a project. + """ + deleteProject("Parameters for DeleteProject" input: DeleteProjectInput!): DeleteProjectPayload + + """ + Deletes a project card. + """ + deleteProjectCard("Parameters for DeleteProjectCard" input: DeleteProjectCardInput!): DeleteProjectCardPayload + + """ + Deletes a project column. + """ + deleteProjectColumn("Parameters for DeleteProjectColumn" input: DeleteProjectColumnInput!): DeleteProjectColumnPayload + + """ + Delete a project. + """ + deleteProjectV2("Parameters for DeleteProjectV2" input: DeleteProjectV2Input!): DeleteProjectV2Payload + + """ + Delete a project field. + """ + deleteProjectV2Field("Parameters for DeleteProjectV2Field" input: DeleteProjectV2FieldInput!): DeleteProjectV2FieldPayload + + """ + Deletes an item from a Project. + """ + deleteProjectV2Item("Parameters for DeleteProjectV2Item" input: DeleteProjectV2ItemInput!): DeleteProjectV2ItemPayload + + """ + Deletes a project status update. + """ + deleteProjectV2StatusUpdate("Parameters for DeleteProjectV2StatusUpdate" input: DeleteProjectV2StatusUpdateInput!): DeleteProjectV2StatusUpdatePayload + + """ + Deletes a project workflow. + """ + deleteProjectV2Workflow("Parameters for DeleteProjectV2Workflow" input: DeleteProjectV2WorkflowInput!): DeleteProjectV2WorkflowPayload + + """ + Deletes a pull request review. + """ + deletePullRequestReview("Parameters for DeletePullRequestReview" input: DeletePullRequestReviewInput!): DeletePullRequestReviewPayload + + """ + Deletes a pull request review comment. + """ + deletePullRequestReviewComment("Parameters for DeletePullRequestReviewComment" input: DeletePullRequestReviewCommentInput!): DeletePullRequestReviewCommentPayload + + """ + Delete a Git Ref. + """ + deleteRef("Parameters for DeleteRef" input: DeleteRefInput!): DeleteRefPayload + + """ + Delete a repository ruleset + """ + deleteRepositoryRuleset("Parameters for DeleteRepositoryRuleset" input: DeleteRepositoryRulesetInput!): DeleteRepositoryRulesetPayload + + """ + Deletes a team discussion. + """ + deleteTeamDiscussion("Parameters for DeleteTeamDiscussion" input: DeleteTeamDiscussionInput!): DeleteTeamDiscussionPayload + + """ + Deletes a team discussion comment. + """ + deleteTeamDiscussionComment("Parameters for DeleteTeamDiscussionComment" input: DeleteTeamDiscussionCommentInput!): DeleteTeamDiscussionCommentPayload + + """ + Deletes a user list. + """ + deleteUserList("Parameters for DeleteUserList" input: DeleteUserListInput!): DeleteUserListPayload + + """ + Deletes a verifiable domain. + """ + deleteVerifiableDomain("Parameters for DeleteVerifiableDomain" input: DeleteVerifiableDomainInput!): DeleteVerifiableDomainPayload + + """ + Remove a pull request from the merge queue. + """ + dequeuePullRequest("Parameters for DequeuePullRequest" input: DequeuePullRequestInput!): DequeuePullRequestPayload + + """ + Disable auto merge on the given pull request + """ + disablePullRequestAutoMerge("Parameters for DisablePullRequestAutoMerge" input: DisablePullRequestAutoMergeInput!): DisablePullRequestAutoMergePayload + + """ + Dismisses an approved or rejected pull request review. + """ + dismissPullRequestReview("Parameters for DismissPullRequestReview" input: DismissPullRequestReviewInput!): DismissPullRequestReviewPayload + + """ + Dismisses the Dependabot alert. + """ + dismissRepositoryVulnerabilityAlert("Parameters for DismissRepositoryVulnerabilityAlert" input: DismissRepositoryVulnerabilityAlertInput!): DismissRepositoryVulnerabilityAlertPayload + + """ + Enable the default auto-merge on a pull request. + """ + enablePullRequestAutoMerge("Parameters for EnablePullRequestAutoMerge" input: EnablePullRequestAutoMergeInput!): EnablePullRequestAutoMergePayload + + """ + Add a pull request to the merge queue. + """ + enqueuePullRequest("Parameters for EnqueuePullRequest" input: EnqueuePullRequestInput!): EnqueuePullRequestPayload + + """ + Follow an organization. + """ + followOrganization("Parameters for FollowOrganization" input: FollowOrganizationInput!): FollowOrganizationPayload + + """ + Follow a user. + """ + followUser("Parameters for FollowUser" input: FollowUserInput!): FollowUserPayload + + """ + Grant the migrator role to a user for all organizations under an enterprise account. + """ + grantEnterpriseOrganizationsMigratorRole("Parameters for GrantEnterpriseOrganizationsMigratorRole" input: GrantEnterpriseOrganizationsMigratorRoleInput!): GrantEnterpriseOrganizationsMigratorRolePayload + + """ + Grant the migrator role to a user or a team. + """ + grantMigratorRole("Parameters for GrantMigratorRole" input: GrantMigratorRoleInput!): GrantMigratorRolePayload + + """ + Creates a new project by importing columns and a list of issues/PRs. + """ + importProject("Parameters for ImportProject" input: ImportProjectInput!): ImportProjectPayload + + """ + Invite someone to become an administrator of the enterprise. + """ + inviteEnterpriseAdmin("Parameters for InviteEnterpriseAdmin" input: InviteEnterpriseAdminInput!): InviteEnterpriseAdminPayload + + """ + Invite someone to become an unaffiliated member of the enterprise. + """ + inviteEnterpriseMember("Parameters for InviteEnterpriseMember" input: InviteEnterpriseMemberInput!): InviteEnterpriseMemberPayload + + """ + Links a project to a repository. + """ + linkProjectV2ToRepository("Parameters for LinkProjectV2ToRepository" input: LinkProjectV2ToRepositoryInput!): LinkProjectV2ToRepositoryPayload + + """ + Links a project to a team. + """ + linkProjectV2ToTeam("Parameters for LinkProjectV2ToTeam" input: LinkProjectV2ToTeamInput!): LinkProjectV2ToTeamPayload + + """ + Creates a repository link for a project. + """ + linkRepositoryToProject("Parameters for LinkRepositoryToProject" input: LinkRepositoryToProjectInput!): LinkRepositoryToProjectPayload + + """ + Lock a lockable object + """ + lockLockable("Parameters for LockLockable" input: LockLockableInput!): LockLockablePayload + + """ + Mark a discussion comment as the chosen answer for discussions in an answerable category. + """ + markDiscussionCommentAsAnswer("Parameters for MarkDiscussionCommentAsAnswer" input: MarkDiscussionCommentAsAnswerInput!): MarkDiscussionCommentAsAnswerPayload + + """ + Mark a pull request file as viewed + """ + markFileAsViewed("Parameters for MarkFileAsViewed" input: MarkFileAsViewedInput!): MarkFileAsViewedPayload + + """ + Marks a notification as done + """ + markNotificationAsDone("Parameters for MarkNotificationAsDone" input: MarkNotificationAsDoneInput!): MarkNotificationAsDonePayload + + """ + Mark a project as a template. Note that only projects which are owned by an Organization can be marked as a template. + """ + markProjectV2AsTemplate("Parameters for MarkProjectV2AsTemplate" input: MarkProjectV2AsTemplateInput!): MarkProjectV2AsTemplatePayload + + """ + Marks a pull request ready for review. + """ + markPullRequestReadyForReview("Parameters for MarkPullRequestReadyForReview" input: MarkPullRequestReadyForReviewInput!): MarkPullRequestReadyForReviewPayload + + """ + Merge a head into a branch. + """ + mergeBranch("Parameters for MergeBranch" input: MergeBranchInput!): MergeBranchPayload + + """ + Merge a pull request. + """ + mergePullRequest("Parameters for MergePullRequest" input: MergePullRequestInput!): MergePullRequestPayload + + """ + Minimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + minimizeComment("Parameters for MinimizeComment" input: MinimizeCommentInput!): MinimizeCommentPayload + + """ + Moves a project card to another place. + """ + moveProjectCard("Parameters for MoveProjectCard" input: MoveProjectCardInput!): MoveProjectCardPayload + + """ + Moves a project column to another place. + """ + moveProjectColumn("Parameters for MoveProjectColumn" input: MoveProjectColumnInput!): MoveProjectColumnPayload + + """ + Pin an environment to a repository + """ + pinEnvironment("Parameters for PinEnvironment" input: PinEnvironmentInput!): PinEnvironmentPayload + + """ + Pin an issue to a repository + """ + pinIssue("Parameters for PinIssue" input: PinIssueInput!): PinIssuePayload + + """ + Publish an existing sponsorship tier that is currently still a draft to a GitHub Sponsors profile. + """ + publishSponsorsTier("Parameters for PublishSponsorsTier" input: PublishSponsorsTierInput!): PublishSponsorsTierPayload + + """ + Regenerates the identity provider recovery codes for an enterprise + """ + regenerateEnterpriseIdentityProviderRecoveryCodes("Parameters for RegenerateEnterpriseIdentityProviderRecoveryCodes" input: RegenerateEnterpriseIdentityProviderRecoveryCodesInput!): RegenerateEnterpriseIdentityProviderRecoveryCodesPayload + + """ + Regenerates a verifiable domain's verification token. + """ + regenerateVerifiableDomainToken("Parameters for RegenerateVerifiableDomainToken" input: RegenerateVerifiableDomainTokenInput!): RegenerateVerifiableDomainTokenPayload + + """ + Reject all pending deployments under one or more environments + """ + rejectDeployments("Parameters for RejectDeployments" input: RejectDeploymentsInput!): RejectDeploymentsPayload + + """ + Removes assignees from an assignable object. + """ + removeAssigneesFromAssignable("Parameters for RemoveAssigneesFromAssignable" input: RemoveAssigneesFromAssignableInput!): RemoveAssigneesFromAssignablePayload + + """ + Removes an administrator from the enterprise. + """ + removeEnterpriseAdmin("Parameters for RemoveEnterpriseAdmin" input: RemoveEnterpriseAdminInput!): RemoveEnterpriseAdminPayload + + """ + Removes the identity provider from an enterprise. Owners of enterprises both with and without Enterprise Managed Users may use this mutation. + """ + removeEnterpriseIdentityProvider("Parameters for RemoveEnterpriseIdentityProvider" input: RemoveEnterpriseIdentityProviderInput!): RemoveEnterpriseIdentityProviderPayload + + """ + Removes a user from all organizations within the enterprise + """ + removeEnterpriseMember("Parameters for RemoveEnterpriseMember" input: RemoveEnterpriseMemberInput!): RemoveEnterpriseMemberPayload + + """ + Removes an organization from the enterprise + """ + removeEnterpriseOrganization("Parameters for RemoveEnterpriseOrganization" input: RemoveEnterpriseOrganizationInput!): RemoveEnterpriseOrganizationPayload + + """ + Removes a support entitlement from an enterprise member. + """ + removeEnterpriseSupportEntitlement("Parameters for RemoveEnterpriseSupportEntitlement" input: RemoveEnterpriseSupportEntitlementInput!): RemoveEnterpriseSupportEntitlementPayload + + """ + Removes labels from a Labelable object. + """ + removeLabelsFromLabelable("Parameters for RemoveLabelsFromLabelable" input: RemoveLabelsFromLabelableInput!): RemoveLabelsFromLabelablePayload + + """ + Removes outside collaborator from all repositories in an organization. + """ + removeOutsideCollaborator("Parameters for RemoveOutsideCollaborator" input: RemoveOutsideCollaboratorInput!): RemoveOutsideCollaboratorPayload + + """ + Removes a reaction from a subject. + """ + removeReaction("Parameters for RemoveReaction" input: RemoveReactionInput!): RemoveReactionPayload + + """ + Removes a star from a Starrable. + """ + removeStar("Parameters for RemoveStar" input: RemoveStarInput!): RemoveStarPayload + + """ + Remove an upvote to a discussion or discussion comment. + """ + removeUpvote("Parameters for RemoveUpvote" input: RemoveUpvoteInput!): RemoveUpvotePayload + + """ + Reopen a discussion. + """ + reopenDiscussion("Parameters for ReopenDiscussion" input: ReopenDiscussionInput!): ReopenDiscussionPayload + + """ + Reopen a issue. + """ + reopenIssue("Parameters for ReopenIssue" input: ReopenIssueInput!): ReopenIssuePayload + + """ + Reopen a pull request. + """ + reopenPullRequest("Parameters for ReopenPullRequest" input: ReopenPullRequestInput!): ReopenPullRequestPayload + + """ + Reorder a pinned repository environment + """ + reorderEnvironment("Parameters for ReorderEnvironment" input: ReorderEnvironmentInput!): ReorderEnvironmentPayload + + """ + Set review requests on a pull request. + """ + requestReviews("Parameters for RequestReviews" input: RequestReviewsInput!): RequestReviewsPayload + + """ + Rerequests an existing check suite. + """ + rerequestCheckSuite("Parameters for RerequestCheckSuite" input: RerequestCheckSuiteInput!): RerequestCheckSuitePayload + + """ + Marks a review thread as resolved. + """ + resolveReviewThread("Parameters for ResolveReviewThread" input: ResolveReviewThreadInput!): ResolveReviewThreadPayload + + """ + Retire a published payment tier from your GitHub Sponsors profile so it cannot be used to start new sponsorships. + """ + retireSponsorsTier("Parameters for RetireSponsorsTier" input: RetireSponsorsTierInput!): RetireSponsorsTierPayload + + """ + Create a pull request that reverts the changes from a merged pull request. + """ + revertPullRequest("Parameters for RevertPullRequest" input: RevertPullRequestInput!): RevertPullRequestPayload + + """ + Revoke the migrator role to a user for all organizations under an enterprise account. + """ + revokeEnterpriseOrganizationsMigratorRole("Parameters for RevokeEnterpriseOrganizationsMigratorRole" input: RevokeEnterpriseOrganizationsMigratorRoleInput!): RevokeEnterpriseOrganizationsMigratorRolePayload + + """ + Revoke the migrator role from a user or a team. + """ + revokeMigratorRole("Parameters for RevokeMigratorRole" input: RevokeMigratorRoleInput!): RevokeMigratorRolePayload + + """ + Creates or updates the identity provider for an enterprise. + """ + setEnterpriseIdentityProvider("Parameters for SetEnterpriseIdentityProvider" input: SetEnterpriseIdentityProviderInput!): SetEnterpriseIdentityProviderPayload + + """ + Set an organization level interaction limit for an organization's public repositories. + """ + setOrganizationInteractionLimit("Parameters for SetOrganizationInteractionLimit" input: SetOrganizationInteractionLimitInput!): SetOrganizationInteractionLimitPayload + + """ + Sets an interaction limit setting for a repository. + """ + setRepositoryInteractionLimit("Parameters for SetRepositoryInteractionLimit" input: SetRepositoryInteractionLimitInput!): SetRepositoryInteractionLimitPayload + + """ + Set a user level interaction limit for an user's public repositories. + """ + setUserInteractionLimit("Parameters for SetUserInteractionLimit" input: SetUserInteractionLimitInput!): SetUserInteractionLimitPayload + + """ + Starts a GitHub Enterprise Importer organization migration. + """ + startOrganizationMigration("Parameters for StartOrganizationMigration" input: StartOrganizationMigrationInput!): StartOrganizationMigrationPayload + + """ + Starts a GitHub Enterprise Importer (GEI) repository migration. + """ + startRepositoryMigration("Parameters for StartRepositoryMigration" input: StartRepositoryMigrationInput!): StartRepositoryMigrationPayload + + """ + Submits a pending pull request review. + """ + submitPullRequestReview("Parameters for SubmitPullRequestReview" input: SubmitPullRequestReviewInput!): SubmitPullRequestReviewPayload + + """ + Transfer an organization from one enterprise to another enterprise. + """ + transferEnterpriseOrganization("Parameters for TransferEnterpriseOrganization" input: TransferEnterpriseOrganizationInput!): TransferEnterpriseOrganizationPayload + + """ + Transfer an issue to a different repository + """ + transferIssue("Parameters for TransferIssue" input: TransferIssueInput!): TransferIssuePayload + + """ + Unarchives a ProjectV2Item + """ + unarchiveProjectV2Item("Parameters for UnarchiveProjectV2Item" input: UnarchiveProjectV2ItemInput!): UnarchiveProjectV2ItemPayload + + """ + Unarchives a repository. + """ + unarchiveRepository("Parameters for UnarchiveRepository" input: UnarchiveRepositoryInput!): UnarchiveRepositoryPayload + + """ + Unfollow an organization. + """ + unfollowOrganization("Parameters for UnfollowOrganization" input: UnfollowOrganizationInput!): UnfollowOrganizationPayload + + """ + Unfollow a user. + """ + unfollowUser("Parameters for UnfollowUser" input: UnfollowUserInput!): UnfollowUserPayload + + """ + Unlinks a project from a repository. + """ + unlinkProjectV2FromRepository("Parameters for UnlinkProjectV2FromRepository" input: UnlinkProjectV2FromRepositoryInput!): UnlinkProjectV2FromRepositoryPayload + + """ + Unlinks a project to a team. + """ + unlinkProjectV2FromTeam("Parameters for UnlinkProjectV2FromTeam" input: UnlinkProjectV2FromTeamInput!): UnlinkProjectV2FromTeamPayload + + """ + Deletes a repository link from a project. + """ + unlinkRepositoryFromProject("Parameters for UnlinkRepositoryFromProject" input: UnlinkRepositoryFromProjectInput!): UnlinkRepositoryFromProjectPayload + + """ + Unlock a lockable object + """ + unlockLockable("Parameters for UnlockLockable" input: UnlockLockableInput!): UnlockLockablePayload + + """ + Unmark a discussion comment as the chosen answer for discussions in an answerable category. + """ + unmarkDiscussionCommentAsAnswer("Parameters for UnmarkDiscussionCommentAsAnswer" input: UnmarkDiscussionCommentAsAnswerInput!): UnmarkDiscussionCommentAsAnswerPayload + + """ + Unmark a pull request file as viewed + """ + unmarkFileAsViewed("Parameters for UnmarkFileAsViewed" input: UnmarkFileAsViewedInput!): UnmarkFileAsViewedPayload + + """ + Unmark an issue as a duplicate of another issue. + """ + unmarkIssueAsDuplicate("Parameters for UnmarkIssueAsDuplicate" input: UnmarkIssueAsDuplicateInput!): UnmarkIssueAsDuplicatePayload + + """ + Unmark a project as a template. + """ + unmarkProjectV2AsTemplate("Parameters for UnmarkProjectV2AsTemplate" input: UnmarkProjectV2AsTemplateInput!): UnmarkProjectV2AsTemplatePayload + + """ + Unminimizes a comment on an Issue, Commit, Pull Request, or Gist + """ + unminimizeComment("Parameters for UnminimizeComment" input: UnminimizeCommentInput!): UnminimizeCommentPayload + + """ + Unpin a pinned issue from a repository + """ + unpinIssue("Parameters for UnpinIssue" input: UnpinIssueInput!): UnpinIssuePayload + + """ + Marks a review thread as unresolved. + """ + unresolveReviewThread("Parameters for UnresolveReviewThread" input: UnresolveReviewThreadInput!): UnresolveReviewThreadPayload + + """ + Unsubscribes from notifications + """ + unsubscribeFromNotifications("Parameters for UnsubscribeFromNotifications" input: UnsubscribeFromNotificationsInput!): UnsubscribeFromNotificationsPayload + + """ + Update a branch protection rule + """ + updateBranchProtectionRule("Parameters for UpdateBranchProtectionRule" input: UpdateBranchProtectionRuleInput!): UpdateBranchProtectionRulePayload + + """ + Update a check run + """ + updateCheckRun("Parameters for UpdateCheckRun" input: UpdateCheckRunInput!): UpdateCheckRunPayload + + """ + Modifies the settings of an existing check suite + """ + updateCheckSuitePreferences("Parameters for UpdateCheckSuitePreferences" input: UpdateCheckSuitePreferencesInput!): UpdateCheckSuitePreferencesPayload + + """ + Update a discussion + """ + updateDiscussion("Parameters for UpdateDiscussion" input: UpdateDiscussionInput!): UpdateDiscussionPayload + + """ + Update the contents of a comment on a Discussion + """ + updateDiscussionComment("Parameters for UpdateDiscussionComment" input: UpdateDiscussionCommentInput!): UpdateDiscussionCommentPayload + + """ + Updates the role of an enterprise administrator. + """ + updateEnterpriseAdministratorRole("Parameters for UpdateEnterpriseAdministratorRole" input: UpdateEnterpriseAdministratorRoleInput!): UpdateEnterpriseAdministratorRolePayload + + """ + Sets whether private repository forks are enabled for an enterprise. + """ + updateEnterpriseAllowPrivateRepositoryForkingSetting("Parameters for UpdateEnterpriseAllowPrivateRepositoryForkingSetting" input: UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput!): UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload + + """ + Sets the base repository permission for organizations in an enterprise. + """ + updateEnterpriseDefaultRepositoryPermissionSetting("Parameters for UpdateEnterpriseDefaultRepositoryPermissionSetting" input: UpdateEnterpriseDefaultRepositoryPermissionSettingInput!): UpdateEnterpriseDefaultRepositoryPermissionSettingPayload + + """ + Sets whether organization members with admin permissions on a repository can change repository visibility. + """ + updateEnterpriseMembersCanChangeRepositoryVisibilitySetting("Parameters for UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting" input: UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput!): UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload + + """ + Sets the members can create repositories setting for an enterprise. + """ + updateEnterpriseMembersCanCreateRepositoriesSetting("Parameters for UpdateEnterpriseMembersCanCreateRepositoriesSetting" input: UpdateEnterpriseMembersCanCreateRepositoriesSettingInput!): UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload + + """ + Sets the members can delete issues setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteIssuesSetting("Parameters for UpdateEnterpriseMembersCanDeleteIssuesSetting" input: UpdateEnterpriseMembersCanDeleteIssuesSettingInput!): UpdateEnterpriseMembersCanDeleteIssuesSettingPayload + + """ + Sets the members can delete repositories setting for an enterprise. + """ + updateEnterpriseMembersCanDeleteRepositoriesSetting("Parameters for UpdateEnterpriseMembersCanDeleteRepositoriesSetting" input: UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput!): UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload + + """ + Sets whether members can invite collaborators are enabled for an enterprise. + """ + updateEnterpriseMembersCanInviteCollaboratorsSetting("Parameters for UpdateEnterpriseMembersCanInviteCollaboratorsSetting" input: UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput!): UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload + + """ + Sets whether or not an organization owner can make purchases. + """ + updateEnterpriseMembersCanMakePurchasesSetting("Parameters for UpdateEnterpriseMembersCanMakePurchasesSetting" input: UpdateEnterpriseMembersCanMakePurchasesSettingInput!): UpdateEnterpriseMembersCanMakePurchasesSettingPayload + + """ + Sets the members can update protected branches setting for an enterprise. + """ + updateEnterpriseMembersCanUpdateProtectedBranchesSetting("Parameters for UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting" input: UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput!): UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload + + """ + Sets the members can view dependency insights for an enterprise. + """ + updateEnterpriseMembersCanViewDependencyInsightsSetting("Parameters for UpdateEnterpriseMembersCanViewDependencyInsightsSetting" input: UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput!): UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload + + """ + Sets whether organization projects are enabled for an enterprise. + """ + updateEnterpriseOrganizationProjectsSetting("Parameters for UpdateEnterpriseOrganizationProjectsSetting" input: UpdateEnterpriseOrganizationProjectsSettingInput!): UpdateEnterpriseOrganizationProjectsSettingPayload + + """ + Updates the role of an enterprise owner with an organization. + """ + updateEnterpriseOwnerOrganizationRole("Parameters for UpdateEnterpriseOwnerOrganizationRole" input: UpdateEnterpriseOwnerOrganizationRoleInput!): UpdateEnterpriseOwnerOrganizationRolePayload + + """ + Updates an enterprise's profile. + """ + updateEnterpriseProfile("Parameters for UpdateEnterpriseProfile" input: UpdateEnterpriseProfileInput!): UpdateEnterpriseProfilePayload + + """ + Sets whether repository projects are enabled for a enterprise. + """ + updateEnterpriseRepositoryProjectsSetting("Parameters for UpdateEnterpriseRepositoryProjectsSetting" input: UpdateEnterpriseRepositoryProjectsSettingInput!): UpdateEnterpriseRepositoryProjectsSettingPayload + + """ + Sets whether team discussions are enabled for an enterprise. + """ + updateEnterpriseTeamDiscussionsSetting("Parameters for UpdateEnterpriseTeamDiscussionsSetting" input: UpdateEnterpriseTeamDiscussionsSettingInput!): UpdateEnterpriseTeamDiscussionsSettingPayload + + """ + Sets whether two factor authentication is required for all users in an enterprise. + """ + updateEnterpriseTwoFactorAuthenticationRequiredSetting("Parameters for UpdateEnterpriseTwoFactorAuthenticationRequiredSetting" input: UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput!): UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload + + """ + Updates an environment. + """ + updateEnvironment("Parameters for UpdateEnvironment" input: UpdateEnvironmentInput!): UpdateEnvironmentPayload + + """ + Sets whether an IP allow list is enabled on an owner. + """ + updateIpAllowListEnabledSetting("Parameters for UpdateIpAllowListEnabledSetting" input: UpdateIpAllowListEnabledSettingInput!): UpdateIpAllowListEnabledSettingPayload + + """ + Updates an IP allow list entry. + """ + updateIpAllowListEntry("Parameters for UpdateIpAllowListEntry" input: UpdateIpAllowListEntryInput!): UpdateIpAllowListEntryPayload + + """ + Sets whether IP allow list configuration for installed GitHub Apps is enabled on an owner. + """ + updateIpAllowListForInstalledAppsEnabledSetting("Parameters for UpdateIpAllowListForInstalledAppsEnabledSetting" input: UpdateIpAllowListForInstalledAppsEnabledSettingInput!): UpdateIpAllowListForInstalledAppsEnabledSettingPayload + + """ + Updates an Issue. + """ + updateIssue("Parameters for UpdateIssue" input: UpdateIssueInput!): UpdateIssuePayload + + """ + Updates an IssueComment object. + """ + updateIssueComment("Parameters for UpdateIssueComment" input: UpdateIssueCommentInput!): UpdateIssueCommentPayload + + """ + Updates an existing label. + """ + updateLabel("Parameters for UpdateLabel" input: UpdateLabelInput!): UpdateLabelPayload + + """ + Update the setting to restrict notifications to only verified or approved domains available to an owner. + """ + updateNotificationRestrictionSetting("Parameters for UpdateNotificationRestrictionSetting" input: UpdateNotificationRestrictionSettingInput!): UpdateNotificationRestrictionSettingPayload + + """ + Sets whether private repository forks are enabled for an organization. + """ + updateOrganizationAllowPrivateRepositoryForkingSetting("Parameters for UpdateOrganizationAllowPrivateRepositoryForkingSetting" input: UpdateOrganizationAllowPrivateRepositoryForkingSettingInput!): UpdateOrganizationAllowPrivateRepositoryForkingSettingPayload + + """ + Sets whether contributors are required to sign off on web-based commits for repositories in an organization. + """ + updateOrganizationWebCommitSignoffSetting("Parameters for UpdateOrganizationWebCommitSignoffSetting" input: UpdateOrganizationWebCommitSignoffSettingInput!): UpdateOrganizationWebCommitSignoffSettingPayload + + """ + Toggle the setting for your GitHub Sponsors profile that allows other GitHub accounts to sponsor you on GitHub while paying for the sponsorship on Patreon. Only applicable when you have a GitHub Sponsors profile and have connected your GitHub account with Patreon. + """ + updatePatreonSponsorability("Parameters for UpdatePatreonSponsorability" input: UpdatePatreonSponsorabilityInput!): UpdatePatreonSponsorabilityPayload + + """ + Updates an existing project. + """ + updateProject("Parameters for UpdateProject" input: UpdateProjectInput!): UpdateProjectPayload + + """ + Updates an existing project card. + """ + updateProjectCard("Parameters for UpdateProjectCard" input: UpdateProjectCardInput!): UpdateProjectCardPayload + + """ + Updates an existing project column. + """ + updateProjectColumn("Parameters for UpdateProjectColumn" input: UpdateProjectColumnInput!): UpdateProjectColumnPayload + + """ + Updates an existing project. + """ + updateProjectV2("Parameters for UpdateProjectV2" input: UpdateProjectV2Input!): UpdateProjectV2Payload + + """ + Update the collaborators on a team or a project + """ + updateProjectV2Collaborators("Parameters for UpdateProjectV2Collaborators" input: UpdateProjectV2CollaboratorsInput!): UpdateProjectV2CollaboratorsPayload + + """ + Updates a draft issue within a Project. + """ + updateProjectV2DraftIssue("Parameters for UpdateProjectV2DraftIssue" input: UpdateProjectV2DraftIssueInput!): UpdateProjectV2DraftIssuePayload + + """ + This mutation updates the value of a field for an item in a Project. Currently only single-select, text, number, date, and iteration fields are supported. + """ + updateProjectV2ItemFieldValue("Parameters for UpdateProjectV2ItemFieldValue" input: UpdateProjectV2ItemFieldValueInput!): UpdateProjectV2ItemFieldValuePayload + + """ + This mutation updates the position of the item in the project, where the position represents the priority of an item. + """ + updateProjectV2ItemPosition("Parameters for UpdateProjectV2ItemPosition" input: UpdateProjectV2ItemPositionInput!): UpdateProjectV2ItemPositionPayload + + """ + Updates a status update within a Project. + """ + updateProjectV2StatusUpdate("Parameters for UpdateProjectV2StatusUpdate" input: UpdateProjectV2StatusUpdateInput!): UpdateProjectV2StatusUpdatePayload + + """ + Update a pull request + """ + updatePullRequest("Parameters for UpdatePullRequest" input: UpdatePullRequestInput!): UpdatePullRequestPayload + + """ + Merge or Rebase HEAD from upstream branch into pull request branch + """ + updatePullRequestBranch("Parameters for UpdatePullRequestBranch" input: UpdatePullRequestBranchInput!): UpdatePullRequestBranchPayload + + """ + Updates the body of a pull request review. + """ + updatePullRequestReview("Parameters for UpdatePullRequestReview" input: UpdatePullRequestReviewInput!): UpdatePullRequestReviewPayload + + """ + Updates a pull request review comment. + """ + updatePullRequestReviewComment("Parameters for UpdatePullRequestReviewComment" input: UpdatePullRequestReviewCommentInput!): UpdatePullRequestReviewCommentPayload + + """ + Update a Git Ref. + """ + updateRef("Parameters for UpdateRef" input: UpdateRefInput!): UpdateRefPayload + + """ + Creates, updates and/or deletes multiple refs in a repository. + + This mutation takes a list of `RefUpdate`s and performs these updates + on the repository. All updates are performed atomically, meaning that + if one of them is rejected, no other ref will be modified. + + `RefUpdate.beforeOid` specifies that the given reference needs to point + to the given value before performing any updates. A value of + `0000000000000000000000000000000000000000` can be used to verify that + the references should not exist. + + `RefUpdate.afterOid` specifies the value that the given reference + will point to after performing all updates. A value of + `0000000000000000000000000000000000000000` can be used to delete a + reference. + + If `RefUpdate.force` is set to `true`, a non-fast-forward updates + for the given reference will be allowed. + """ + updateRefs("Parameters for UpdateRefs" input: UpdateRefsInput!): UpdateRefsPayload + + """ + Update information about a repository. + """ + updateRepository("Parameters for UpdateRepository" input: UpdateRepositoryInput!): UpdateRepositoryPayload + + """ + Update a repository ruleset + """ + updateRepositoryRuleset("Parameters for UpdateRepositoryRuleset" input: UpdateRepositoryRulesetInput!): UpdateRepositoryRulesetPayload + + """ + Sets whether contributors are required to sign off on web-based commits for a repository. + """ + updateRepositoryWebCommitSignoffSetting("Parameters for UpdateRepositoryWebCommitSignoffSetting" input: UpdateRepositoryWebCommitSignoffSettingInput!): UpdateRepositoryWebCommitSignoffSettingPayload + + """ + Change visibility of your sponsorship and opt in or out of email updates from the maintainer. + """ + updateSponsorshipPreferences("Parameters for UpdateSponsorshipPreferences" input: UpdateSponsorshipPreferencesInput!): UpdateSponsorshipPreferencesPayload + + """ + Updates the state for subscribable subjects. + """ + updateSubscription("Parameters for UpdateSubscription" input: UpdateSubscriptionInput!): UpdateSubscriptionPayload + + """ + Updates a team discussion. + """ + updateTeamDiscussion("Parameters for UpdateTeamDiscussion" input: UpdateTeamDiscussionInput!): UpdateTeamDiscussionPayload + + """ + Updates a discussion comment. + """ + updateTeamDiscussionComment("Parameters for UpdateTeamDiscussionComment" input: UpdateTeamDiscussionCommentInput!): UpdateTeamDiscussionCommentPayload + + """ + Updates team review assignment. + """ + updateTeamReviewAssignment("Parameters for UpdateTeamReviewAssignment" input: UpdateTeamReviewAssignmentInput!): UpdateTeamReviewAssignmentPayload + + """ + Update team repository. + """ + updateTeamsRepository("Parameters for UpdateTeamsRepository" input: UpdateTeamsRepositoryInput!): UpdateTeamsRepositoryPayload + + """ + Replaces the repository's topics with the given topics. + """ + updateTopics("Parameters for UpdateTopics" input: UpdateTopicsInput!): UpdateTopicsPayload + + """ + Updates an existing user list. + """ + updateUserList("Parameters for UpdateUserList" input: UpdateUserListInput!): UpdateUserListPayload + + """ + Updates which of the viewer's lists an item belongs to + """ + updateUserListsForItem("Parameters for UpdateUserListsForItem" input: UpdateUserListsForItemInput!): UpdateUserListsForItemPayload + + """ + Verify that a verifiable domain has the expected DNS record. + """ + verifyVerifiableDomain("Parameters for VerifyVerifiableDomain" input: VerifyVerifiableDomainInput!): VerifyVerifiableDomainPayload +} + +""" +An object with an ID. +""" +interface Node { + """ + ID of the object. + """ + id: ID! +} + +""" +The possible values for the notification restriction setting. +""" +enum NotificationRestrictionSettingValue { + """ + The setting is enabled for the owner. + """ + ENABLED + + """ + The setting is disabled for the owner. + """ + DISABLED +} + +""" +An OIDC identity provider configured to provision identities for an enterprise. Visible to enterprise owners or enterprise owners' personal access tokens (classic) with read:enterprise or admin:enterprise scope. +""" +type OIDCProvider implements Node { + """ + The enterprise this identity provider belongs to. + """ + enterprise: Enterprise + + """ + ExternalIdentities provisioned by this identity provider. + """ + externalIdentities("Filter to external identities with valid org membership only" membersOnly: Boolean, "Filter to external identities with the users login" login: String, "Filter to external identities with the users userName\/NameID attribute" userName: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ExternalIdentityConnection! + + """ + The Node ID of the OIDCProvider object + """ + id: ID! + + """ + The OIDC identity provider type + """ + providerType: OIDCProviderType! + + """ + The id of the tenant this provider is attached to + """ + tenantId: String! +} + +""" +The OIDC identity provider type +""" +enum OIDCProviderType { + """ + Azure Active Directory + """ + AAD +} + +""" +Metadata for an audit entry with action oauth_application.* +""" +interface OauthApplicationAuditEntryData { + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI +} + +""" +Audit log entry for a oauth_application.create event. +""" +type OauthApplicationCreateAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The application URL of the OAuth application. + """ + applicationUrl: URI + + """ + The callback URL of the OAuth application. + """ + callbackUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OauthApplicationCreateAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The rate limit of the OAuth application. + """ + rateLimit: Int + + """ + The state of the OAuth application. + """ + state: OauthApplicationCreateAuditEntryState + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The state of an OAuth application when it was created. +""" +enum OauthApplicationCreateAuditEntryState { + """ + The OAuth application was active and allowed to have OAuth Accesses. + """ + ACTIVE + + """ + The OAuth application was suspended from generating OAuth Accesses due to abuse or security concerns. + """ + SUSPENDED + + """ + The OAuth application was in the process of being deleted. + """ + PENDING_DELETION +} + +""" +The corresponding operation type for the action +""" +enum OperationType { + """ + An existing resource was accessed + """ + ACCESS + + """ + A resource performed an authentication event + """ + AUTHENTICATION + + """ + A new resource was created + """ + CREATE + + """ + An existing resource was modified + """ + MODIFY + + """ + An existing resource was removed + """ + REMOVE + + """ + An existing resource was restored + """ + RESTORE + + """ + An existing resource was transferred between multiple resources + """ + TRANSFER +} + +""" +Possible directions in which to order a list of items when provided an `orderBy` argument. +""" +enum OrderDirection { + """ + Specifies an ascending order for a given `orderBy` argument. + """ + ASC + + """ + Specifies a descending order for a given `orderBy` argument. + """ + DESC +} + +""" +Audit log entry for a org.add_billing_manager +""" +type OrgAddBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgAddBillingManagerAuditEntry object + """ + id: ID! + + """ + The email address used to invite a billing manager for the organization. + """ + invitationEmail: String + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.add_member +""" +type OrgAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgAddMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The permission level of the member added to the organization. + """ + permission: OrgAddMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgAddMemberAuditEntryPermission { + """ + Can read and clone repositories. + """ + READ + + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN +} + +""" +Audit log entry for a org.block_user +""" +type OrgBlockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The blocked user. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgBlockUserAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.disable_collaborators_only event. +""" +type OrgConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgConfigDisableCollaboratorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.config.enable_collaborators_only event. +""" +type OrgConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgConfigEnableCollaboratorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.create event. +""" +type OrgCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The billing plan for the Organization. + """ + billingPlan: OrgCreateAuditEntryBillingPlan + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgCreateAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The billing plans available for organizations. +""" +enum OrgCreateAuditEntryBillingPlan { + """ + Free Plan + """ + FREE + + """ + Team Plan + """ + BUSINESS + + """ + Enterprise Cloud Plan + """ + BUSINESS_PLUS + + """ + Legacy Unlimited Plan + """ + UNLIMITED + + """ + Tiered Per Seat Plan + """ + TIERED_PER_SEAT +} + +""" +Audit log entry for a org.disable_oauth_app_restrictions event. +""" +type OrgDisableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgDisableOauthAppRestrictionsAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_saml event. +""" +type OrgDisableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + + """ + The Node ID of the OrgDisableSamlAuditEntry object + """ + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.disable_two_factor_requirement event. +""" +type OrgDisableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgDisableTwoFactorRequirementAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_oauth_app_restrictions event. +""" +type OrgEnableOauthAppRestrictionsAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgEnableOauthAppRestrictionsAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_saml event. +""" +type OrgEnableSamlAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The SAML provider's digest algorithm URL. + """ + digestMethodUrl: URI + + """ + The Node ID of the OrgEnableSamlAuditEntry object + """ + id: ID! + + """ + The SAML provider's issuer URL. + """ + issuerUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The SAML provider's signature algorithm URL. + """ + signatureMethodUrl: URI + + """ + The SAML provider's single sign-on URL. + """ + singleSignOnUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.enable_two_factor_requirement event. +""" +type OrgEnableTwoFactorRequirementAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgEnableTwoFactorRequirementAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Ordering options for an organization's enterprise owner connections. +""" +input OrgEnterpriseOwnerOrder { + """ + The field to order enterprise owners by. + """ + field: OrgEnterpriseOwnerOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which enterprise owners can be ordered. +""" +enum OrgEnterpriseOwnerOrderField { + """ + Order enterprise owners by login. + """ + LOGIN +} + +""" +Audit log entry for a org.invite_member event. +""" +type OrgInviteMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The email address of the organization invitation. + """ + email: String + + """ + The Node ID of the OrgInviteMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The organization invitation. + """ + organizationInvitation: OrganizationInvitation + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.invite_to_business event. +""" +type OrgInviteToBusinessAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the OrgInviteToBusinessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_approved event. +""" +type OrgOauthAppAccessApprovedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgOauthAppAccessApprovedAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_blocked event. +""" +type OrgOauthAppAccessBlockedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgOauthAppAccessBlockedAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_denied event. +""" +type OrgOauthAppAccessDeniedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgOauthAppAccessDeniedAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_requested event. +""" +type OrgOauthAppAccessRequestedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgOauthAppAccessRequestedAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.oauth_app_access_unblocked event. +""" +type OrgOauthAppAccessUnblockedAuditEntry implements AuditEntry & Node & OauthApplicationAuditEntryData & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgOauthAppAccessUnblockedAuditEntry object + """ + id: ID! + + """ + The name of the OAuth application. + """ + oauthApplicationName: String + + """ + The HTTP path for the OAuth application + """ + oauthApplicationResourcePath: URI + + """ + The HTTP URL for the OAuth application + """ + oauthApplicationUrl: URI + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.remove_billing_manager event. +""" +type OrgRemoveBillingManagerAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgRemoveBillingManagerAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the billing manager being removed. + """ + reason: OrgRemoveBillingManagerAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reason a billing manager was removed from an Organization. +""" +enum OrgRemoveBillingManagerAuditEntryReason { + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE + + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY +} + +""" +Audit log entry for a org.remove_member event. +""" +type OrgRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgRemoveMemberAuditEntry object + """ + id: ID! + + """ + The types of membership the member has with the organization. + """ + membershipTypes: [OrgRemoveMemberAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the member being removed. + """ + reason: OrgRemoveMemberAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveMemberAuditEntryMembershipType { + """ + A suspended member. + """ + SUSPENDED + + """ + A direct member is a user that is a member of the Organization. + """ + DIRECT_MEMBER + + """ + Organization owners have full access and can change several settings, including the names of repositories that belong to the Organization and Owners team membership. In addition, organization owners can delete the organization and all of its repositories. + """ + ADMIN + + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER + + """ + An unaffiliated collaborator is a person who is not a member of the Organization and does not have access to any repositories in the Organization. + """ + UNAFFILIATED + + """ + An outside collaborator is a person who isn't explicitly a member of the Organization, but who has Read, Write, or Admin permissions to one or more repositories in the organization. + """ + OUTSIDE_COLLABORATOR +} + +""" +The reason a member was removed from an Organization. +""" +enum OrgRemoveMemberAuditEntryReason { + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE + + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING + + """ + SAML SSO enforcement requires an external identity + """ + SAML_SSO_ENFORCEMENT_REQUIRES_EXTERNAL_IDENTITY + + """ + User account has been deleted + """ + USER_ACCOUNT_DELETED + + """ + User was removed from organization during account recovery + """ + TWO_FACTOR_ACCOUNT_RECOVERY +} + +""" +Audit log entry for a org.remove_outside_collaborator event. +""" +type OrgRemoveOutsideCollaboratorAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgRemoveOutsideCollaboratorAuditEntry object + """ + id: ID! + + """ + The types of membership the outside collaborator has with the organization. + """ + membershipTypes: [OrgRemoveOutsideCollaboratorAuditEntryMembershipType!] + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The reason for the outside collaborator being removed from the Organization. + """ + reason: OrgRemoveOutsideCollaboratorAuditEntryReason + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The type of membership a user has with an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryMembershipType { + """ + An outside collaborator is a person who isn't explicitly a member of the Organization, but who has Read, Write, or Admin permissions to one or more repositories in the organization. + """ + OUTSIDE_COLLABORATOR + + """ + An unaffiliated collaborator is a person who is not a member of the Organization and does not have access to any repositories in the organization. + """ + UNAFFILIATED + + """ + A billing manager is a user who manages the billing settings for the Organization, such as updating payment information. + """ + BILLING_MANAGER +} + +""" +The reason an outside collaborator was removed from an Organization. +""" +enum OrgRemoveOutsideCollaboratorAuditEntryReason { + """ + The organization required 2FA of its billing managers and this user did not have 2FA enabled. + """ + TWO_FACTOR_REQUIREMENT_NON_COMPLIANCE + + """ + SAML external identity missing + """ + SAML_EXTERNAL_IDENTITY_MISSING +} + +""" +Audit log entry for a org.restore_member event. +""" +type OrgRestoreMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgRestoreMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The number of custom email routings for the restored member. + """ + restoredCustomEmailRoutingsCount: Int + + """ + The number of issue assignments for the restored member. + """ + restoredIssueAssignmentsCount: Int + + """ + Restored organization membership objects. + """ + restoredMemberships: [OrgRestoreMemberAuditEntryMembership!] + + """ + The number of restored memberships. + """ + restoredMembershipsCount: Int + + """ + The number of repositories of the restored member. + """ + restoredRepositoriesCount: Int + + """ + The number of starred repositories for the restored member. + """ + restoredRepositoryStarsCount: Int + + """ + The number of watched repositories for the restored member. + """ + restoredRepositoryWatchesCount: Int + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Types of memberships that can be restored for an Organization member. +""" +union OrgRestoreMemberAuditEntryMembership = OrgRestoreMemberMembershipOrganizationAuditEntryData|OrgRestoreMemberMembershipRepositoryAuditEntryData|OrgRestoreMemberMembershipTeamAuditEntryData + +""" +Metadata for an organization membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipOrganizationAuditEntryData implements OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +Metadata for a repository membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipRepositoryAuditEntryData implements RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +Metadata for a team membership for org.restore_member actions +""" +type OrgRestoreMemberMembershipTeamAuditEntryData implements TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a org.unblock_user +""" +type OrgUnblockUserAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The user being unblocked by the organization. + """ + blockedUser: User + + """ + The username of the blocked user. + """ + blockedUserName: String + + """ + The HTTP path for the blocked user. + """ + blockedUserResourcePath: URI + + """ + The HTTP URL for the blocked user. + """ + blockedUserUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgUnblockUserAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a org.update_default_repository_permission +""" +type OrgUpdateDefaultRepositoryPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgUpdateDefaultRepositoryPermissionAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new base repository permission level for the organization. + """ + permission: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The former base repository permission level for the organization. + """ + permissionWas: OrgUpdateDefaultRepositoryPermissionAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The default permission a repository can have in an Organization. +""" +enum OrgUpdateDefaultRepositoryPermissionAuditEntryPermission { + """ + Can read and clone repositories. + """ + READ + + """ + Can read, clone and push to repositories. + """ + WRITE + + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN + + """ + No default permission value. + """ + NONE +} + +""" +Audit log entry for a org.update_member event. +""" +type OrgUpdateMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgUpdateMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new member permission level for the organization. + """ + permission: OrgUpdateMemberAuditEntryPermission + + """ + The former member permission level for the organization. + """ + permissionWas: OrgUpdateMemberAuditEntryPermission + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The permissions available to members on an Organization. +""" +enum OrgUpdateMemberAuditEntryPermission { + """ + Can read and clone repositories. + """ + READ + + """ + Can read, clone, push, and add collaborators to repositories. + """ + ADMIN +} + +""" +Audit log entry for a org.update_member_repository_creation_permission event. +""" +type OrgUpdateMemberRepositoryCreationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can members create repositories in the organization. + """ + canCreateRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgUpdateMemberRepositoryCreationPermissionAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The permission for visibility level of repositories for this organization. + """ + visibility: OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility +} + +""" +The permissions available for repository creation on an Organization. +""" +enum OrgUpdateMemberRepositoryCreationPermissionAuditEntryVisibility { + """ + All organization members are restricted from creating any repositories. + """ + ALL + + """ + All organization members are restricted from creating public repositories. + """ + PUBLIC + + """ + All organization members are allowed to create any repositories. + """ + NONE + + """ + All organization members are restricted from creating private repositories. + """ + PRIVATE + + """ + All organization members are restricted from creating internal repositories. + """ + INTERNAL + + """ + All organization members are restricted from creating public or internal repositories. + """ + PUBLIC_INTERNAL + + """ + All organization members are restricted from creating private or internal repositories. + """ + PRIVATE_INTERNAL + + """ + All organization members are restricted from creating public or private repositories. + """ + PUBLIC_PRIVATE +} + +""" +Audit log entry for a org.update_member_repository_invitation_permission event. +""" +type OrgUpdateMemberRepositoryInvitationPermissionAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + Can outside collaborators be invited to repositories in the organization. + """ + canInviteOutsideCollaboratorsToRepositories: Boolean + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the OrgUpdateMemberRepositoryInvitationPermissionAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +An account on GitHub, with one or more owners, that has repositories, members and teams. +""" +type Organization implements Actor & AnnouncementBanner & MemberStatusable & Node & PackageOwner & ProfileOwner & ProjectOwner & ProjectV2Owner & ProjectV2Recent & RepositoryDiscussionAuthor & RepositoryDiscussionCommentAuthor & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + The text of the announcement + """ + announcement: String + + """ + The date the announcement was created + """ + announcementCreatedAt: DateTime + + """ + The expiration date of the announcement, if any + """ + announcementExpiresAt: DateTime + + """ + Whether the announcement can be dismissed by the user + """ + announcementUserDismissible: Boolean + + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems("Filter to only a particular kind of pinnable item." type: PinnableItemType): Boolean! + + """ + Identifies the date and time when the organization was archived. + """ + archivedAt: DateTime + + """ + Audit log entries of the organization + """ + auditLog("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The query string to filter audit entries" query: String, "Ordering options for the returned audit log entries." orderBy: AuditLogOrder = { + field: CREATED_AT + direction: DESC + } + ): OrganizationAuditEntryConnection! + + """ + A URL pointing to the organization's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The organization's public profile description. + """ + description: String + + """ + The organization's public profile description rendered to HTML. + """ + descriptionHTML: String + + """ + A list of domains owned by the organization. + """ + domains("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter by if the domain is verified." isVerified: Boolean = null, "Filter by if the domain is approved." isApproved: Boolean = null, "Ordering options for verifiable domains returned." orderBy: VerifiableDomainOrder = { + field: DOMAIN + direction: ASC + } + ): VerifiableDomainConnection + + """ + The organization's public email. + """ + email: String + + """ + A list of owners of the organization's enterprise account. + """ + enterpriseOwners("The search string to look for." query: String, "The organization role to filter by." organizationRole: RoleInOrganization, "Ordering options for enterprise owners returned from the connection." orderBy: OrgEnterpriseOwnerOrder = { + field: LOGIN + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationEnterpriseOwnerConnection! + + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + The Node ID of the Organization object + """ + id: ID! + + """ + The interaction ability settings for this organization. + """ + interactionAbility: RepositoryInteractionAbility + + """ + The setting value for whether the organization has an IP allow list enabled. + """ + ipAllowListEnabledSetting: IpAllowListEnabledSettingValue! + + """ + The IP addresses that are allowed to access resources owned by the organization. + """ + ipAllowListEntries("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for IP allow list entries returned." orderBy: IpAllowListEntryOrder = { + field: ALLOW_LIST_VALUE + direction: ASC + } + ): IpAllowListEntryConnection! + + """ + The setting value for whether the organization has IP allow list configuration for installed GitHub Apps enabled. + """ + ipAllowListForInstalledAppsEnabledSetting: IpAllowListForInstalledAppsEnabledSettingValue! + + """ + Whether the given account is sponsoring this user/organization. + """ + isSponsoredBy("The target account's login." accountLogin: String!): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether the organization has verified its profile email and website. + """ + isVerified: Boolean! + + """ + Showcases a selection of repositories and gists that the profile owner has either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + Calculate how much each sponsor has ever paid total to this maintainer via GitHub Sponsors. Does not include sponsorships paid via Patreon. + """ + lifetimeReceivedSponsorshipValues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for results returned from the connection." orderBy: SponsorAndLifetimeValueOrder = { + field: SPONSOR_LOGIN + direction: ASC + } + ): SponsorAndLifetimeValueConnection! + + """ + The organization's public profile location. + """ + location: String + + """ + The organization's login name. + """ + login: String! + + """ + A list of all mannequins for this organization. + """ + mannequins("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter mannequins by login." login: String, "Ordering options for mannequins returned from the connection." orderBy: MannequinOrder = { + field: CREATED_AT + direction: ASC + } + ): MannequinConnection! + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for user statuses returned from the connection." orderBy: UserStatusOrder = { + field: UPDATED_AT + direction: DESC + } + ): UserStatusConnection! + + """ + Members can fork private repositories in this organization + """ + membersCanForkPrivateRepositories: Boolean! + + """ + A list of users who are members of this organization. + """ + membersWithRole("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationMemberConnection! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + The organization's public profile name. + """ + name: String + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + Indicates if email notification delivery for this organization is restricted to verified or approved domains. + """ + notificationDeliveryRestrictionEnabledSetting: NotificationRestrictionSettingValue! + + """ + The billing email for the organization. + """ + organizationBillingEmail: String + + """ + A list of packages under the owner. + """ + packages("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Find packages by their names." names: [String], "Find packages in a repository by ID." repositoryId: ID, "Filter registry package by type." packageType: PackageType, "Ordering of the returned packages." orderBy: PackageOrder = { + field: CREATED_AT + direction: DESC + } + ): PackageConnection! + + """ + A list of users who have been invited to join this organization. + """ + pendingMembers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems("Filter the types of pinnable items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems("Filter the types of pinned items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project("The project number to find." number: Int!): Project + + """ + Find a project by number. + """ + projectV2("The project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projects("Ordering options for projects returned from the connection" orderBy: ProjectOrder, "Query to search projects by, currently only searching by name." search: String, "A list of states to filter the projects by." states: [ProjectState!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectConnection! + + """ + The HTTP path listing organization's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing organization's projects + """ + projectsUrl: URI! + + """ + A list of projects under the owner. + """ + projectsV2("A project to search for under the the owner." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + A list of repositories that the user owns. + """ + repositories("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Array of viewer's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the current viewer owns." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If non-null, filters repositories according to whether they are archived and not maintained" isArchived: Boolean, "If non-null, filters repositories according to whether they are forks of another repository" isFork: Boolean): RepositoryConnection! + + """ + Find Repository. + """ + repository("Name of Repository to find." name: String!, "Follow repository renames. If disabled, a repository referenced by its old name will return an error." followRenames: Boolean = true): Repository + + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter discussion comments to only those in a specific repository." repositoryId: ID, "Filter discussion comments to only those that were marked as the answer" onlyAnswers: Boolean = false): DiscussionCommentConnection! + + """ + Discussions this user has started. + """ + repositoryDiscussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for discussions returned from the connection." orderBy: DiscussionOrder = { + field: CREATED_AT + direction: DESC + } + , "Filter discussions to only those in a specific repository." repositoryId: ID, "Filter discussions to only those that have been answered or not. Defaults to including both answered and unanswered discussions." answered: Boolean = null, "A list of states to filter the discussions by." states: [DiscussionState!] = []): DiscussionConnection! + + """ + A list of all repository migrations for this organization. + """ + repositoryMigrations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter repository migrations by state." state: MigrationState, "Filter repository migrations by repository name." repositoryName: String, "Ordering options for repository migrations returned." orderBy: RepositoryMigrationOrder = { + field: CREATED_AT + direction: ASC + } + ): RepositoryMigrationConnection! + + """ + When true the organization requires all members, billing managers, and outside collaborators to enable two-factor authentication. + """ + requiresTwoFactorAuthentication: Boolean + + """ + The HTTP path for this organization. + """ + resourcePath: URI! + + """ + Returns a single ruleset from the current organization by ID. + """ + ruleset("The ID of the ruleset to be returned." databaseId: Int!): RepositoryRuleset + + """ + A list of rulesets for this organization. + """ + rulesets("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Return rulesets configured at higher levels that apply to this organization" includeParents: Boolean = true): RepositoryRulesetConnection + + """ + The Organization's SAML identity provider. Visible to (1) organization owners, (2) organization owners' personal access tokens (classic) with read:org or admin:org scope, (3) GitHub App with an installation token with read or write access to members. + """ + samlIdentityProvider: OrganizationIdentityProvider + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the users and organizations returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If given, will filter for sponsors at the given tier. Will only return sponsors whose tier the viewer is permitted to see." tierId: ID, "Ordering options for sponsors returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter activities returned to only those that occurred in the most recent specified time period. Set to ALL to avoid filtering by when the activity occurred. Will be ignored if `since` or `until` is given." period: SponsorsActivityPeriod = MONTH, "Filter activities to those that occurred on or after this time." since: DateTime, "Filter activities to those that occurred before this time." until: DateTime, "Ordering options for activity returned from the connection." orderBy: SponsorsActivityOrder = { + field: TIMESTAMP + direction: DESC + } + , "Filter activities to only the specified actions." actions: [SponsorsActivityAction!] = [], "Whether to include those events where this sponsorable acted as the sponsor. Defaults to only including events where this sponsorable was the recipient of a sponsorship." includeAsSponsor: Boolean = false, "Whether or not to include private activities in the result set. Defaults to including public and private activities." includePrivate: Boolean = true): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the sponsorship where you're the sponsor. + """ + sponsorshipForViewerAsSponsor("Whether to return the sponsorship only if it's still active. Pass false to get the viewer's sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the sponsorship you're receiving. + """ + sponsorshipForViewerAsSponsorable("Whether to return the sponsorship only if it's still active. Pass false to get the sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorship updates returned from the connection." orderBy: SponsorshipNewsletterOrder = { + field: CREATED_AT + direction: DESC + } + ): SponsorshipNewsletterConnection! + + """ + The sponsorships where this user or organization is the maintainer receiving the funds. + """ + sponsorshipsAsMaintainer("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Whether or not to include private sponsorships in the result set" includePrivate: Boolean = false, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Whether to include only sponsorships that are active right now, versus all sponsorships this maintainer has ever received." activeOnly: Boolean = true): SponsorshipConnection! + + """ + The sponsorships where this user or organization is the funder. + """ + sponsorshipsAsSponsor("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Filter sponsorships returned to those for the specified maintainers. That is, the recipient of the sponsorship is a user or organization with one of the given logins." maintainerLogins: [String!], "Whether to include only sponsorships that are active right now, versus all sponsorships this sponsor has ever made." activeOnly: Boolean = true): SponsorshipConnection! + + """ + Find an organization's team by its slug. + """ + team("The name or slug of the team to find." slug: String!): Team + + """ + A list of teams in this organization. + """ + teams("If non-null, filters teams according to privacy" privacy: TeamPrivacy, "If non-null, filters teams according to notification setting" notificationSetting: TeamNotificationSetting, "If non-null, filters teams according to whether the viewer is an admin or member on team" role: TeamRole, "If non-null, filters teams with query on team name and team slug" query: String, "User logins to filter by" userLogins: [String!], "Ordering options for teams returned from the connection" orderBy: TeamOrder, "If true, filters teams that are mapped to an LDAP Group (Enterprise only)" ldapMapped: Boolean, "If true, restrict to only root teams" rootTeamsOnly: Boolean = false, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): TeamConnection! + + """ + The HTTP path listing organization's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL listing organization's teams + """ + teamsUrl: URI! + + """ + The amount in United States cents (e.g., 500 = $5.00 USD) that this entity has spent on GitHub to fund sponsorships. Only returns a value when viewed by the user themselves or by a user who can manage sponsorships for the requested organization. + """ + totalSponsorshipAmountAsSponsorInCents("Filter payments to those that occurred on or after this time." since: DateTime, "Filter payments to those that occurred before this time." until: DateTime, "Filter payments to those made to the users or organizations with the specified usernames." sponsorableLogins: [String!] = []): Int + + """ + The organization's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this organization. + """ + url: URI! + + """ + Verified domains available to this organization. + """ + verifiedDomainsList: [String!]! + + """ + Organization is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Viewer can create repositories on this organization + """ + viewerCanCreateRepositories: Boolean! + + """ + Viewer can create teams on this organization. + """ + viewerCanCreateTeams: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Viewer is an active member of this organization. + """ + viewerIsAMember: Boolean! + + """ + Whether or not this Organization is followed by the viewer. + """ + viewerIsFollowing: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + Whether contributors are required to sign off on web-based commits for repositories in this organization. + """ + webCommitSignoffRequired: Boolean! + + """ + The organization's public profile URL. + """ + websiteUrl: URI +} + +""" +An audit entry in an organization audit log. +""" +union OrganizationAuditEntry = MembersCanDeleteReposClearAuditEntry|MembersCanDeleteReposDisableAuditEntry|MembersCanDeleteReposEnableAuditEntry|OauthApplicationCreateAuditEntry|OrgAddBillingManagerAuditEntry|OrgAddMemberAuditEntry|OrgBlockUserAuditEntry|OrgConfigDisableCollaboratorsOnlyAuditEntry|OrgConfigEnableCollaboratorsOnlyAuditEntry|OrgCreateAuditEntry|OrgDisableOauthAppRestrictionsAuditEntry|OrgDisableSamlAuditEntry|OrgDisableTwoFactorRequirementAuditEntry|OrgEnableOauthAppRestrictionsAuditEntry|OrgEnableSamlAuditEntry|OrgEnableTwoFactorRequirementAuditEntry|OrgInviteMemberAuditEntry|OrgInviteToBusinessAuditEntry|OrgOauthAppAccessApprovedAuditEntry|OrgOauthAppAccessBlockedAuditEntry|OrgOauthAppAccessDeniedAuditEntry|OrgOauthAppAccessRequestedAuditEntry|OrgOauthAppAccessUnblockedAuditEntry|OrgRemoveBillingManagerAuditEntry|OrgRemoveMemberAuditEntry|OrgRemoveOutsideCollaboratorAuditEntry|OrgRestoreMemberAuditEntry|OrgUnblockUserAuditEntry|OrgUpdateDefaultRepositoryPermissionAuditEntry|OrgUpdateMemberAuditEntry|OrgUpdateMemberRepositoryCreationPermissionAuditEntry|OrgUpdateMemberRepositoryInvitationPermissionAuditEntry|PrivateRepositoryForkingDisableAuditEntry|PrivateRepositoryForkingEnableAuditEntry|RepoAccessAuditEntry|RepoAddMemberAuditEntry|RepoAddTopicAuditEntry|RepoArchivedAuditEntry|RepoChangeMergeSettingAuditEntry|RepoConfigDisableAnonymousGitAccessAuditEntry|RepoConfigDisableCollaboratorsOnlyAuditEntry|RepoConfigDisableContributorsOnlyAuditEntry|RepoConfigDisableSockpuppetDisallowedAuditEntry|RepoConfigEnableAnonymousGitAccessAuditEntry|RepoConfigEnableCollaboratorsOnlyAuditEntry|RepoConfigEnableContributorsOnlyAuditEntry|RepoConfigEnableSockpuppetDisallowedAuditEntry|RepoConfigLockAnonymousGitAccessAuditEntry|RepoConfigUnlockAnonymousGitAccessAuditEntry|RepoCreateAuditEntry|RepoDestroyAuditEntry|RepoRemoveMemberAuditEntry|RepoRemoveTopicAuditEntry|RepositoryVisibilityChangeDisableAuditEntry|RepositoryVisibilityChangeEnableAuditEntry|TeamAddMemberAuditEntry|TeamAddRepositoryAuditEntry|TeamChangeParentTeamAuditEntry|TeamRemoveMemberAuditEntry|TeamRemoveRepositoryAuditEntry + +""" +The connection type for OrganizationAuditEntry. +""" +type OrganizationAuditEntryConnection { + """ + A list of edges. + """ + edges: [OrganizationAuditEntryEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationAuditEntry] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Metadata for an audit entry with action org.* +""" +interface OrganizationAuditEntryData { + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI +} + +""" +An edge in a connection. +""" +type OrganizationAuditEntryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationAuditEntry +} + +""" +A list of organizations managed by an enterprise. +""" +type OrganizationConnection { + """ + A list of edges. + """ + edges: [OrganizationEdge] + + """ + A list of nodes. + """ + nodes: [Organization] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Organization +} + +""" +The connection type for User. +""" +type OrganizationEnterpriseOwnerConnection { + """ + A list of edges. + """ + edges: [OrganizationEnterpriseOwnerEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An enterprise owner in the context of an organization that is part of the enterprise. +""" +type OrganizationEnterpriseOwnerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User + + """ + The role of the owner with respect to the organization. + """ + organizationRole: RoleInOrganization! +} + +""" +An Identity Provider configured to provision SAML and SCIM identities for Organizations. Visible to (1) organization owners, (2) organization owners' personal access tokens (classic) with read:org or admin:org scope, (3) GitHub App with an installation token with read or write access to members. +""" +type OrganizationIdentityProvider implements Node { + """ + The digest algorithm used to sign SAML requests for the Identity Provider. + """ + digestMethod: URI + + """ + External Identities provisioned by this Identity Provider + """ + externalIdentities("Filter to external identities with valid org membership only" membersOnly: Boolean, "Filter to external identities with the users login" login: String, "Filter to external identities with the users userName\/NameID attribute" userName: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ExternalIdentityConnection! + + """ + The Node ID of the OrganizationIdentityProvider object + """ + id: ID! + + """ + The x509 certificate used by the Identity Provider to sign assertions and responses. + """ + idpCertificate: X509Certificate + + """ + The Issuer Entity ID for the SAML Identity Provider + """ + issuer: String + + """ + Organization this Identity Provider belongs to + """ + organization: Organization + + """ + The signature algorithm used to sign SAML requests for the Identity Provider. + """ + signatureMethod: URI + + """ + The URL endpoint for the Identity Provider's SAML SSO. + """ + ssoUrl: URI +} + +""" +An Invitation for a user to an organization. +""" +type OrganizationInvitation implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The email address of the user invited to the organization. + """ + email: String + + """ + The Node ID of the OrganizationInvitation object + """ + id: ID! + + """ + The source of the invitation. + """ + invitationSource: OrganizationInvitationSource! + + """ + The type of invitation that was sent (e.g. email, user). + """ + invitationType: OrganizationInvitationType! + + """ + The user who was invited to the organization. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! @deprecated(reason: "`inviter` will be removed. `inviter` will be replaced by `inviterActor`. Removal on 2024-07-01 UTC.") + + """ + The user who created the invitation. + """ + inviterActor: User + + """ + The organization the invite is for + """ + organization: Organization! + + """ + The user's pending role in the organization (e.g. member, owner). + """ + role: OrganizationInvitationRole! +} + +""" +The connection type for OrganizationInvitation. +""" +type OrganizationInvitationConnection { + """ + A list of edges. + """ + edges: [OrganizationInvitationEdge] + + """ + A list of nodes. + """ + nodes: [OrganizationInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type OrganizationInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: OrganizationInvitation +} + +""" +The possible organization invitation roles. +""" +enum OrganizationInvitationRole { + """ + The user is invited to be a direct member of the organization. + """ + DIRECT_MEMBER + + """ + The user is invited to be an admin of the organization. + """ + ADMIN + + """ + The user is invited to be a billing manager of the organization. + """ + BILLING_MANAGER + + """ + The user's previous role will be reinstated. + """ + REINSTATE +} + +""" +The possible organization invitation sources. +""" +enum OrganizationInvitationSource { + """ + The invitation was sent before this feature was added + """ + UNKNOWN + + """ + The invitation was created from the web interface or from API + """ + MEMBER + + """ + The invitation was created from SCIM + """ + SCIM +} + +""" +The possible organization invitation types. +""" +enum OrganizationInvitationType { + """ + The invitation was to an existing user. + """ + USER + + """ + The invitation was to an email address. + """ + EMAIL +} + +""" +A list of users who belong to the organization. +""" +type OrganizationMemberConnection { + """ + A list of edges. + """ + edges: [OrganizationMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user within an organization. +""" +type OrganizationMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + Whether the organization member has two factor enabled or not. Returns null if information is not available to viewer. + """ + hasTwoFactorEnabled: Boolean + + """ + The item at the end of the edge. + """ + node: User + + """ + The role this user has in the organization. + """ + role: OrganizationMemberRole +} + +""" +The possible roles within an organization for its members. +""" +enum OrganizationMemberRole { + """ + The user is a member of the organization. + """ + MEMBER + + """ + The user is an administrator of the organization. + """ + ADMIN +} + +""" +The possible values for the members can create repositories setting on an organization. +""" +enum OrganizationMembersCanCreateRepositoriesSettingValue { + """ + Members will be able to create public and private repositories. + """ + ALL + + """ + Members will be able to create only private repositories. + """ + PRIVATE + + """ + Members will be able to create only internal repositories. + """ + INTERNAL + + """ + Members will not be able to create public or private repositories. + """ + DISABLED +} + +""" +A GitHub Enterprise Importer (GEI) organization migration. +""" +type OrganizationMigration implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: String + + """ + The reason the organization migration failed. + """ + failureReason: String + + """ + The Node ID of the OrganizationMigration object + """ + id: ID! + + """ + The remaining amount of repos to be migrated. + """ + remainingRepositoriesCount: Int + + """ + The name of the source organization to be migrated. + """ + sourceOrgName: String! + + """ + The URL of the source organization to migrate. + """ + sourceOrgUrl: URI! + + """ + The migration state. + """ + state: OrganizationMigrationState! + + """ + The name of the target organization. + """ + targetOrgName: String! + + """ + The total amount of repositories to be migrated. + """ + totalRepositoriesCount: Int +} + +""" +The Octoshift Organization migration state. +""" +enum OrganizationMigrationState { + """ + The Octoshift migration has not started. + """ + NOT_STARTED + + """ + The Octoshift migration has been queued. + """ + QUEUED + + """ + The Octoshift migration is in progress. + """ + IN_PROGRESS + + """ + The Octoshift migration is performing pre repository migrations. + """ + PRE_REPO_MIGRATION + + """ + The Octoshift org migration is performing repository migrations. + """ + REPO_MIGRATION + + """ + The Octoshift migration is performing post repository migrations. + """ + POST_REPO_MIGRATION + + """ + The Octoshift migration has succeeded. + """ + SUCCEEDED + + """ + The Octoshift migration has failed. + """ + FAILED + + """ + The Octoshift migration needs to have its credentials validated. + """ + PENDING_VALIDATION + + """ + The Octoshift migration has invalid credentials. + """ + FAILED_VALIDATION +} + +""" +Used for argument of CreateProjectV2 mutation. +""" +union OrganizationOrUser = Organization|User + +""" +Ordering options for organization connections. +""" +input OrganizationOrder { + """ + The field to order organizations by. + """ + field: OrganizationOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which organization connections can be ordered. +""" +enum OrganizationOrderField { + """ + Order organizations by creation time + """ + CREATED_AT + + """ + Order organizations by login + """ + LOGIN +} + +""" +An organization teams hovercard context +""" +type OrganizationTeamsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Teams in this organization the user is a member of that are relevant + """ + relevantTeams("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): TeamConnection! + + """ + The path for the full team list for this user + """ + teamsResourcePath: URI! + + """ + The URL for the full team list for this user + """ + teamsUrl: URI! + + """ + The total number of teams the user is on in the organization + """ + totalTeamCount: Int! +} + +""" +An organization list hovercard context +""" +type OrganizationsHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Organizations this user is a member of that are relevant + """ + relevantOrganizations("Ordering options for the User's organizations." orderBy: OrganizationOrder = null, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationConnection! + + """ + The total number of organizations this user is in + """ + totalOrganizationCount: Int! +} + +""" +Information for an uploaded package. +""" +type Package implements Node { + """ + The Node ID of the Package object + """ + id: ID! + + """ + Find the latest version for the package. + """ + latestVersion: PackageVersion + + """ + Identifies the name of the package. + """ + name: String! + + """ + Identifies the type of the package. + """ + packageType: PackageType! + + """ + The repository this package belongs to. + """ + repository: Repository + + """ + Statistics about package activity. + """ + statistics: PackageStatistics + + """ + Find package version by version string. + """ + version("The package version." version: String!): PackageVersion + + """ + list of versions for this package + """ + versions("Ordering of the returned packages." orderBy: PackageVersionOrder = { + field: CREATED_AT + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PackageVersionConnection! +} + +""" +The connection type for Package. +""" +type PackageConnection { + """ + A list of edges. + """ + edges: [PackageEdge] + + """ + A list of nodes. + """ + nodes: [Package] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Package +} + +""" +A file in a package version. +""" +type PackageFile implements Node { + """ + The Node ID of the PackageFile object + """ + id: ID! + + """ + MD5 hash of the file. + """ + md5: String + + """ + Name of the file. + """ + name: String! + + """ + The package version this file belongs to. + """ + packageVersion: PackageVersion + + """ + SHA1 hash of the file. + """ + sha1: String + + """ + SHA256 hash of the file. + """ + sha256: String + + """ + Size of the file in bytes. + """ + size: Int + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + URL to download the asset. + """ + url: URI +} + +""" +The connection type for PackageFile. +""" +type PackageFileConnection { + """ + A list of edges. + """ + edges: [PackageFileEdge] + + """ + A list of nodes. + """ + nodes: [PackageFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageFile +} + +""" +Ways in which lists of package files can be ordered upon return. +""" +input PackageFileOrder { + """ + The field in which to order package files by. + """ + field: PackageFileOrderField + + """ + The direction in which to order package files by the specified field. + """ + direction: OrderDirection +} + +""" +Properties by which package file connections can be ordered. +""" +enum PackageFileOrderField { + """ + Order package files by creation time + """ + CREATED_AT +} + +""" +Ways in which lists of packages can be ordered upon return. +""" +input PackageOrder { + """ + The field in which to order packages by. + """ + field: PackageOrderField + + """ + The direction in which to order packages by the specified field. + """ + direction: OrderDirection +} + +""" +Properties by which package connections can be ordered. +""" +enum PackageOrderField { + """ + Order packages by creation time + """ + CREATED_AT +} + +""" +Represents an owner of a package. +""" +interface PackageOwner { + """ + The Node ID of the PackageOwner object + """ + id: ID! + + """ + A list of packages under the owner. + """ + packages("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Find packages by their names." names: [String], "Find packages in a repository by ID." repositoryId: ID, "Filter registry package by type." packageType: PackageType, "Ordering of the returned packages." orderBy: PackageOrder = { + field: CREATED_AT + direction: DESC + } + ): PackageConnection! +} + +""" +Represents a object that contains package activity statistics such as downloads. +""" +type PackageStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +A version tag contains the mapping between a tag name and a version. +""" +type PackageTag implements Node { + """ + The Node ID of the PackageTag object + """ + id: ID! + + """ + Identifies the tag name of the version. + """ + name: String! + + """ + Version that the tag is associated with. + """ + version: PackageVersion +} + +""" +The possible types of a package. +""" +enum PackageType { + """ + An npm package. + """ + NPM @deprecated(reason: "NPM will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2022-11-21 UTC.") + + """ + A rubygems package. + """ + RUBYGEMS @deprecated(reason: "RUBYGEMS will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2022-12-28 UTC.") + + """ + A maven package. + """ + MAVEN @deprecated(reason: "MAVEN will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2023-02-10 UTC.") + + """ + A docker image. + """ + DOCKER @deprecated(reason: "DOCKER will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2021-06-21 UTC.") + + """ + A debian package. + """ + DEBIAN + + """ + A nuget package. + """ + NUGET @deprecated(reason: "NUGET will be removed from this enum as this type will be migrated to only be used by the Packages REST API. Removal on 2022-11-21 UTC.") + + """ + A python package. + """ + PYPI +} + +""" +Information about a specific package version. +""" +type PackageVersion implements Node { + """ + List of files associated with this package version + """ + files("Ordering of the returned package files." orderBy: PackageFileOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PackageFileConnection! + + """ + The Node ID of the PackageVersion object + """ + id: ID! + + """ + The package associated with this version. + """ + package: Package + + """ + The platform this version was built for. + """ + platform: String + + """ + Whether or not this version is a pre-release. + """ + preRelease: Boolean! + + """ + The README of this package version. + """ + readme: String + + """ + The release associated with this package version. + """ + release: Release + + """ + Statistics about package activity. + """ + statistics: PackageVersionStatistics + + """ + The package version summary. + """ + summary: String + + """ + The version string. + """ + version: String! +} + +""" +The connection type for PackageVersion. +""" +type PackageVersionConnection { + """ + A list of edges. + """ + edges: [PackageVersionEdge] + + """ + A list of nodes. + """ + nodes: [PackageVersion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PackageVersionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PackageVersion +} + +""" +Ways in which lists of package versions can be ordered upon return. +""" +input PackageVersionOrder { + """ + The field in which to order package versions by. + """ + field: PackageVersionOrderField + + """ + The direction in which to order package versions by the specified field. + """ + direction: OrderDirection +} + +""" +Properties by which package version connections can be ordered. +""" +enum PackageVersionOrderField { + """ + Order package versions by creation time + """ + CREATED_AT +} + +""" +Represents a object that contains package version activity statistics such as downloads. +""" +type PackageVersionStatistics { + """ + Number of times the package was downloaded since it was created. + """ + downloadsTotalCount: Int! +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String + + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String +} + +""" +The possible types of patch statuses. +""" +enum PatchStatus { + """ + The file was added. Git status 'A'. + """ + ADDED + + """ + The file was deleted. Git status 'D'. + """ + DELETED + + """ + The file was renamed. Git status 'R'. + """ + RENAMED + + """ + The file was copied. Git status 'C'. + """ + COPIED + + """ + The file's contents were changed. Git status 'M'. + """ + MODIFIED + + """ + The file's type was changed. Git status 'T'. + """ + CHANGED +} + +""" +Types that can grant permissions on a repository to a user +""" +union PermissionGranter = Organization|Repository|Team + +""" +A level of permission and source for a user's access to a repository. +""" +type PermissionSource { + """ + The organization the repository belongs to. + """ + organization: Organization! + + """ + The level of access this source has granted to the user. + """ + permission: DefaultRepositoryPermissionField! + + """ + The name of the role this source has granted to the user. + """ + roleName: String + + """ + The source of this permission. + """ + source: PermissionGranter! +} + +""" +Autogenerated input type of PinEnvironment +""" +input PinEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the environment to modify + """ + environmentId: ID! + + """ + The desired state of the environment. If true, environment will be pinned. If false, it will be unpinned. + """ + pinned: Boolean! +} + +""" +Autogenerated return type of PinEnvironment. +""" +type PinEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The environment that was pinned + """ + environment: Environment + + """ + The pinned environment if we pinned + """ + pinnedEnvironment: PinnedEnvironment +} + +""" +Autogenerated input type of PinIssue +""" +input PinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be pinned + """ + issueId: ID! +} + +""" +Autogenerated return type of PinIssue. +""" +type PinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was pinned + """ + issue: Issue +} + +""" +Types that can be pinned to a profile page. +""" +union PinnableItem = Gist|Repository + +""" +The connection type for PinnableItem. +""" +type PinnableItemConnection { + """ + A list of edges. + """ + edges: [PinnableItemEdge] + + """ + A list of nodes. + """ + nodes: [PinnableItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnableItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnableItem +} + +""" +Represents items that can be pinned to a profile page or dashboard. +""" +enum PinnableItemType { + """ + A repository. + """ + REPOSITORY + + """ + A gist. + """ + GIST + + """ + An issue. + """ + ISSUE + + """ + A project. + """ + PROJECT + + """ + A pull request. + """ + PULL_REQUEST + + """ + A user. + """ + USER + + """ + An organization. + """ + ORGANIZATION + + """ + A team. + """ + TEAM +} + +""" +A Pinned Discussion is a discussion pinned to a repository's index page. +""" +type PinnedDiscussion implements Node & RepositoryNode { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The discussion that was pinned. + """ + discussion: Discussion! + + """ + Color stops of the chosen gradient + """ + gradientStopColors: [String!]! + + """ + The Node ID of the PinnedDiscussion object + """ + id: ID! + + """ + Background texture pattern + """ + pattern: PinnedDiscussionPattern! + + """ + The actor that pinned this discussion. + """ + pinnedBy: Actor! + + """ + Preconfigured background gradient option + """ + preconfiguredGradient: PinnedDiscussionGradient + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for PinnedDiscussion. +""" +type PinnedDiscussionConnection { + """ + A list of edges. + """ + edges: [PinnedDiscussionEdge] + + """ + A list of nodes. + """ + nodes: [PinnedDiscussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedDiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedDiscussion +} + +""" +Preconfigured gradients that may be used to style discussions pinned within a repository. +""" +enum PinnedDiscussionGradient { + """ + A gradient of red to orange + """ + RED_ORANGE + + """ + A gradient of blue to mint + """ + BLUE_MINT + + """ + A gradient of blue to purple + """ + BLUE_PURPLE + + """ + A gradient of pink to blue + """ + PINK_BLUE + + """ + A gradient of purple to coral + """ + PURPLE_CORAL +} + +""" +Preconfigured background patterns that may be used to style discussions pinned within a repository. +""" +enum PinnedDiscussionPattern { + """ + A solid dot pattern + """ + DOT_FILL + + """ + A plus sign pattern + """ + PLUS + + """ + A lightning bolt pattern + """ + ZAP + + """ + An upward-facing chevron pattern + """ + CHEVRON_UP + + """ + A hollow dot pattern + """ + DOT + + """ + A heart pattern + """ + HEART_FILL +} + +""" +Represents a pinned environment on a given repository +""" +type PinnedEnvironment implements Node { + """ + Identifies the date and time when the pinned environment was created + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Identifies the environment associated. + """ + environment: Environment! + + """ + The Node ID of the PinnedEnvironment object + """ + id: ID! + + """ + Identifies the position of the pinned environment. + """ + position: Int! + + """ + The repository that this environment was pinned to. + """ + repository: Repository! +} + +""" +The connection type for PinnedEnvironment. +""" +type PinnedEnvironmentConnection { + """ + A list of edges. + """ + edges: [PinnedEnvironmentEdge] + + """ + A list of nodes. + """ + nodes: [PinnedEnvironment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedEnvironmentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedEnvironment +} + +""" +Ordering options for pinned environments +""" +input PinnedEnvironmentOrder { + """ + The field to order pinned environments by. + """ + field: PinnedEnvironmentOrderField! + + """ + The direction in which to order pinned environments by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which pinned environments connections can be ordered +""" +enum PinnedEnvironmentOrderField { + """ + Order pinned environments by position + """ + POSITION +} + +""" +Represents a 'pinned' event on a given issue or pull request. +""" +type PinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the PinnedEvent object + """ + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +A Pinned Issue is a issue pinned to a repository's index page. +""" +type PinnedIssue implements Node { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The Node ID of the PinnedIssue object + """ + id: ID! + + """ + The issue that was pinned. + """ + issue: Issue! + + """ + The actor that pinned this issue. + """ + pinnedBy: Actor! + + """ + The repository that this issue was pinned to. + """ + repository: Repository! +} + +""" +The connection type for PinnedIssue. +""" +type PinnedIssueConnection { + """ + A list of edges. + """ + edges: [PinnedIssueEdge] + + """ + A list of nodes. + """ + nodes: [PinnedIssue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PinnedIssueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PinnedIssue +} + +""" +An ISO-8601 encoded UTC date string with millisecond precision. +""" +scalar PreciseDateTime + +""" +Audit log entry for a private_repository_forking.disable event. +""" +type PrivateRepositoryForkingDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the PrivateRepositoryForkingDisableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a private_repository_forking.enable event. +""" +type PrivateRepositoryForkingEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the PrivateRepositoryForkingEnableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A curatable list of repositories relating to a repository owner, which defaults to showing the most popular repositories they own. +""" +type ProfileItemShowcase { + """ + Whether or not the owner has pinned any repositories or gists. + """ + hasPinnedItems: Boolean! + + """ + The repositories and gists in the showcase. If the profile owner has any pinned items, those will be returned. Otherwise, the profile owner's popular repositories will be returned. + """ + items("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! +} + +""" +Represents any entity on GitHub that has a profile page. +""" +interface ProfileOwner { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems("Filter to only a particular kind of pinnable item." type: PinnableItemType): Boolean! + + """ + The public profile email. + """ + email: String + + """ + The Node ID of the ProfileOwner object + """ + id: ID! + + """ + Showcases a selection of repositories and gists that the profile owner has either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + The public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The public profile name. + """ + name: String + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems("Filter the types of pinnable items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems("Filter the types of pinned items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + The public profile website URL. + """ + websiteUrl: URI +} + +""" +Projects manage issues, pull requests and notes within a project owner. +""" +type Project implements Closable & Node & Updatable { + """ + The project's description body. + """ + body: String + + """ + The projects description body rendered to HTML. + """ + bodyHTML: HTML! + + """ + Indicates if the object is closed (definition of closed may depend on type) + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + List of columns in the project + """ + columns("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectColumnConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who originally created the project. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Project object + """ + id: ID! + + """ + The project's name. + """ + name: String! + + """ + The project's number. + """ + number: Int! + + """ + The project's owner. Currently limited to repositories, organizations, and users. + """ + owner: ProjectOwner! + + """ + List of pending cards in this project + """ + pendingCards("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A list of archived states to filter the cards by" archivedStates: [ProjectCardArchivedState] = [ARCHIVED,NOT_ARCHIVED]): ProjectCardConnection! + + """ + Project progress details. + """ + progress: ProjectProgress! + + """ + The HTTP path for this project + """ + resourcePath: URI! + + """ + Whether the project is open or closed. + """ + state: ProjectState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project + """ + url: URI! + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +A card in a project. +""" +type ProjectCard implements Node { + """ + The project column this card is associated under. A card may only belong to one + project column at a time. The column field will be null if the card is created + in a pending state and has yet to be associated with a column. Once cards are + associated with a column, they will not become pending in the future. + """ + column: ProjectColumn + + """ + The card content item + """ + content: ProjectCardItem + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this card + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectCard object + """ + id: ID! + + """ + Whether the card is archived + """ + isArchived: Boolean! + + """ + The card note + """ + note: String + + """ + The project that contains this card. + """ + project: Project! + + """ + The HTTP path for this card + """ + resourcePath: URI! + + """ + The state of ProjectCard + """ + state: ProjectCardState + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this card + """ + url: URI! +} + +""" +The possible archived states of a project card. +""" +enum ProjectCardArchivedState { + """ + A project card that is archived + """ + ARCHIVED + + """ + A project card that is not archived + """ + NOT_ARCHIVED +} + +""" +The connection type for ProjectCard. +""" +type ProjectCardConnection { + """ + A list of edges. + """ + edges: [ProjectCardEdge] + + """ + A list of nodes. + """ + nodes: [ProjectCard] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectCardEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectCard +} + +""" +An issue or PR and its owning repository to be used in a project card. +""" +input ProjectCardImport { + """ + Repository name with owner (owner/repository). + """ + repository: String! + + """ + The issue or pull request number. + """ + number: Int! +} + +""" +Types that can be inside Project Cards. +""" +union ProjectCardItem = Issue|PullRequest + +""" +Various content states of a ProjectCard +""" +enum ProjectCardState { + """ + The card has content only. + """ + CONTENT_ONLY + + """ + The card has a note only. + """ + NOTE_ONLY + + """ + The card is redacted. + """ + REDACTED +} + +""" +A column inside a project. +""" +type ProjectColumn implements Node { + """ + List of cards in the column + """ + cards("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A list of archived states to filter the cards by" archivedStates: [ProjectCardArchivedState] = [ARCHIVED,NOT_ARCHIVED]): ProjectCardConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectColumn object + """ + id: ID! + + """ + The project column's name. + """ + name: String! + + """ + The project that contains this column. + """ + project: Project! + + """ + The semantic purpose of the column + """ + purpose: ProjectColumnPurpose + + """ + The HTTP path for this project column + """ + resourcePath: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project column + """ + url: URI! +} + +""" +The connection type for ProjectColumn. +""" +type ProjectColumnConnection { + """ + A list of edges. + """ + edges: [ProjectColumnEdge] + + """ + A list of nodes. + """ + nodes: [ProjectColumn] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectColumnEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectColumn +} + +""" +A project column and a list of its issues and PRs. +""" +input ProjectColumnImport { + """ + The name of the column. + """ + columnName: String! + + """ + The position of the column, starting from 0. + """ + position: Int! + + """ + A list of issues and pull requests in the column. + """ + issues: [ProjectCardImport!] +} + +""" +The semantic purpose of the column - todo, in progress, or done. +""" +enum ProjectColumnPurpose { + """ + The column contains cards still to be worked on + """ + TODO + + """ + The column contains cards which are currently being worked on + """ + IN_PROGRESS + + """ + The column contains cards which are complete + """ + DONE +} + +""" +A list of projects associated with the owner. +""" +type ProjectConnection { + """ + A list of edges. + """ + edges: [ProjectEdge] + + """ + A list of nodes. + """ + nodes: [Project] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Project +} + +""" +Ways in which lists of projects can be ordered upon return. +""" +input ProjectOrder { + """ + The field in which to order projects by. + """ + field: ProjectOrderField! + + """ + The direction in which to order projects by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which project connections can be ordered. +""" +enum ProjectOrderField { + """ + Order projects by creation time + """ + CREATED_AT + + """ + Order projects by update time + """ + UPDATED_AT + + """ + Order projects by name + """ + NAME +} + +""" +Represents an owner of a Project. +""" +interface ProjectOwner { + """ + The Node ID of the ProjectOwner object + """ + id: ID! + + """ + Find project by number. + """ + project("The project number to find." number: Int!): Project + + """ + A list of projects under the owner. + """ + projects("Ordering options for projects returned from the connection" orderBy: ProjectOrder, "Query to search projects by, currently only searching by name." search: String, "A list of states to filter the projects by." states: [ProjectState!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectConnection! + + """ + The HTTP path listing owners projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing owners projects + """ + projectsUrl: URI! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! +} + +""" +Project progress stats. +""" +type ProjectProgress { + """ + The number of done cards. + """ + doneCount: Int! + + """ + The percentage of done cards. + """ + donePercentage: Float! + + """ + Whether progress tracking is enabled and cards with purpose exist for this project + """ + enabled: Boolean! + + """ + The number of in-progress cards. + """ + inProgressCount: Int! + + """ + The percentage of in-progress cards. + """ + inProgressPercentage: Float! + + """ + The number of to do cards. + """ + todoCount: Int! + + """ + The percentage of to do cards. + """ + todoPercentage: Float! +} + +""" +State of the project; either 'open' or 'closed' +""" +enum ProjectState { + """ + The project is open. + """ + OPEN + + """ + The project is closed. + """ + CLOSED +} + +""" +GitHub-provided templates for Projects +""" +enum ProjectTemplate { + """ + Create a board with columns for To do, In progress and Done. + """ + BASIC_KANBAN + + """ + Create a board with v2 triggers to automatically move cards across To do, In progress and Done columns. + """ + AUTOMATED_KANBAN_V2 + + """ + Create a board with triggers to automatically move cards across columns with review automation. + """ + AUTOMATED_REVIEWS_KANBAN + + """ + Create a board to triage and prioritize bugs with To do, priority, and Done columns. + """ + BUG_TRIAGE +} + +""" +New projects that manage issues, pull requests and drafts using tables and boards. +""" +type ProjectV2 implements Closable & Node & Updatable { + """ + Returns true if the project is closed. + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who originally created the project. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + A field of the project + """ + field("The name of the field" name: String!): ProjectV2FieldConfiguration + + """ + List of fields and their constraints in the project + """ + fields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for project v2 fields returned from the connection" orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConfigurationConnection! + + """ + The Node ID of the ProjectV2 object + """ + id: ID! + + """ + List of items in the project + """ + items("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for project v2 items returned from the connection" orderBy: ProjectV2ItemOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2ItemConnection! + + """ + The project's number. + """ + number: Int! + + """ + The project's owner. Currently limited to organizations and users. + """ + owner: ProjectV2Owner! + + """ + Returns true if the project is public. + """ + public: Boolean! + + """ + The project's readme. + """ + readme: String + + """ + The repositories the project is linked to. + """ + repositories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder = { + field: CREATED_AT + direction: DESC + } + ): RepositoryConnection! + + """ + The HTTP path for this project + """ + resourcePath: URI! + + """ + The project's short description. + """ + shortDescription: String + + """ + List of the status updates in the project. + """ + statusUpdates("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: ProjectV2StatusOrder = { + field: CREATED_AT + direction: DESC + } + ): ProjectV2StatusUpdateConnection! + + """ + The teams the project is linked to. + """ + teams("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for teams returned from this connection." orderBy: TeamOrder = { + field: NAME + direction: ASC + } + ): TeamConnection! + + """ + Returns true if this project is a template. + """ + template: Boolean! + + """ + The project's name. + """ + title: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this project + """ + url: URI! + + """ + A view of the project + """ + view("The number of a view belonging to the project" number: Int!): ProjectV2View + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + List of views in the project + """ + views("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for project v2 views returned from the connection" orderBy: ProjectV2ViewOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2ViewConnection! + + """ + A workflow of the project + """ + workflow("The number of a workflow belonging to the project" number: Int!): ProjectV2Workflow + + """ + List of the workflows in the project + """ + workflows("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for project v2 workflows returned from the connection" orderBy: ProjectV2WorkflowOrder = { + field: NAME + direction: ASC + } + ): ProjectV2WorkflowConnection! +} + +""" +Possible collaborators for a project. +""" +union ProjectV2Actor = Team|User + +""" +The connection type for ProjectV2Actor. +""" +type ProjectV2ActorConnection { + """ + A list of edges. + """ + edges: [ProjectV2ActorEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Actor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2ActorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Actor +} + +""" +A collaborator to update on a project. Only one of the userId or teamId should be provided. +""" +input ProjectV2Collaborator { + """ + The ID of the user as a collaborator. + """ + userId: ID + + """ + The ID of the team as a collaborator. + """ + teamId: ID + + """ + The role to grant the collaborator + """ + role: ProjectV2Roles! +} + +""" +The connection type for ProjectV2. +""" +type ProjectV2Connection { + """ + A list of edges. + """ + edges: [ProjectV2Edge] + + """ + A list of nodes. + """ + nodes: [ProjectV2] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The type of a project field. +""" +enum ProjectV2CustomFieldType { + """ + Text + """ + TEXT + + """ + Single Select + """ + SINGLE_SELECT + + """ + Number + """ + NUMBER + + """ + Date + """ + DATE +} + +""" +An edge in a connection. +""" +type ProjectV2Edge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2 +} + +""" +A field inside a project. +""" +type ProjectV2Field implements Node & ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectV2Field object + """ + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Common fields across different project field types +""" +interface ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectV2FieldCommon object + """ + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Configurations for project fields. +""" +union ProjectV2FieldConfiguration = ProjectV2Field|ProjectV2IterationField|ProjectV2SingleSelectField + +""" +The connection type for ProjectV2FieldConfiguration. +""" +type ProjectV2FieldConfigurationConnection { + """ + A list of edges. + """ + edges: [ProjectV2FieldConfigurationEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2FieldConfiguration] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2FieldConfigurationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2FieldConfiguration +} + +""" +The connection type for ProjectV2Field. +""" +type ProjectV2FieldConnection { + """ + A list of edges. + """ + edges: [ProjectV2FieldEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Field] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2FieldEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Field +} + +""" +Ordering options for project v2 field connections +""" +input ProjectV2FieldOrder { + """ + The field to order the project v2 fields by. + """ + field: ProjectV2FieldOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which project v2 field connections can be ordered. +""" +enum ProjectV2FieldOrderField { + """ + Order project v2 fields by position + """ + POSITION + + """ + Order project v2 fields by creation time + """ + CREATED_AT + + """ + Order project v2 fields by name + """ + NAME +} + +""" +The type of a project field. +""" +enum ProjectV2FieldType { + """ + Assignees + """ + ASSIGNEES + + """ + Linked Pull Requests + """ + LINKED_PULL_REQUESTS + + """ + Reviewers + """ + REVIEWERS + + """ + Labels + """ + LABELS + + """ + Milestone + """ + MILESTONE + + """ + Repository + """ + REPOSITORY + + """ + Title + """ + TITLE + + """ + Text + """ + TEXT + + """ + Single Select + """ + SINGLE_SELECT + + """ + Number + """ + NUMBER + + """ + Date + """ + DATE + + """ + Iteration + """ + ITERATION + + """ + Tracks + """ + TRACKS + + """ + Tracked by + """ + TRACKED_BY +} + +""" +The values that can be used to update a field of an item inside a Project. Only 1 value can be updated at a time. +""" +input ProjectV2FieldValue { + """ + The text to set on the field. + """ + text: String + + """ + The number to set on the field. + """ + number: Float + + """ + The ISO 8601 date to set on the field. + """ + date: Date + + """ + The id of the single select option to set on the field. + """ + singleSelectOptionId: String + + """ + The id of the iteration to set on the field. + """ + iterationId: String +} + +""" +Ways in which to filter lists of projects. +""" +input ProjectV2Filters { + """ + List project v2 filtered by the state given. + """ + state: ProjectV2State +} + +""" +An item within a Project. +""" +type ProjectV2Item implements Node { + """ + The content of the referenced draft issue, issue, or pull request + """ + content: ProjectV2ItemContent + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The field value of the first project field which matches the 'name' argument that is set on the item. + """ + fieldValueByName("The name of the field to return the field value of" name: String!): ProjectV2ItemFieldValue + + """ + The field values that are set on the item. + """ + fieldValues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for project v2 item field values returned from the connection" orderBy: ProjectV2ItemFieldValueOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2ItemFieldValueConnection! + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The Node ID of the ProjectV2Item object + """ + id: ID! + + """ + Whether the item is archived. + """ + isArchived: Boolean! + + """ + The project that contains this item. + """ + project: ProjectV2! + + """ + The type of the item. + """ + type: ProjectV2ItemType! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2Item. +""" +type ProjectV2ItemConnection { + """ + A list of edges. + """ + edges: [ProjectV2ItemEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Item] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Types that can be inside Project Items. +""" +union ProjectV2ItemContent = DraftIssue|Issue|PullRequest + +""" +An edge in a connection. +""" +type ProjectV2ItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Item +} + +""" +The value of a date field in a Project item. +""" +type ProjectV2ItemFieldDateValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Date value for the field + """ + date: Date + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldDateValue object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of an iteration field in a Project item. +""" +type ProjectV2ItemFieldIterationValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The duration of the iteration in days. + """ + duration: Int! + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldIterationValue object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + The ID of the iteration. + """ + iterationId: String! + + """ + The start date of the iteration. + """ + startDate: Date! + + """ + The title of the iteration. + """ + title: String! + + """ + The title of the iteration, with HTML. + """ + titleHTML: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of the labels field in a Project item. +""" +type ProjectV2ItemFieldLabelValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + Labels value of a field + """ + labels("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection +} + +""" +The value of a milestone field in a Project item. +""" +type ProjectV2ItemFieldMilestoneValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + Milestone value of a field + """ + milestone: Milestone +} + +""" +The value of a number field in a Project item. +""" +type ProjectV2ItemFieldNumberValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldNumberValue object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Number as a float(8) + """ + number: Float + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a pull request field in a Project item. +""" +type ProjectV2ItemFieldPullRequestValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The pull requests for this field + """ + pullRequests("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for pull requests." orderBy: PullRequestOrder = { + field: CREATED_AT + direction: ASC + } + ): PullRequestConnection +} + +""" +The value of a repository field in a Project item. +""" +type ProjectV2ItemFieldRepositoryValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The repository for this field. + """ + repository: Repository +} + +""" +The value of a reviewers field in a Project item. +""" +type ProjectV2ItemFieldReviewerValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The reviewers for this field. + """ + reviewers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RequestedReviewerConnection +} + +""" +The value of a single select field in a Project item. +""" +type ProjectV2ItemFieldSingleSelectValue implements Node & ProjectV2ItemFieldValueCommon { + """ + The color applied to the selected single-select option. + """ + color: ProjectV2SingleSelectFieldOptionColor! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + A plain-text description of the selected single-select option, such as what the option means. + """ + description: String + + """ + The description of the selected single-select option, including HTML tags. + """ + descriptionHTML: String + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldSingleSelectValue object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + The name of the selected single select option. + """ + name: String + + """ + The html name of the selected single select option. + """ + nameHTML: String + + """ + The id of the selected single select option. + """ + optionId: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a text field in a Project item. +""" +type ProjectV2ItemFieldTextValue implements Node & ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldTextValue object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Text value of a field + """ + text: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The value of a user field in a Project item. +""" +type ProjectV2ItemFieldUserValue { + """ + The field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The users for this field + """ + users("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection +} + +""" +Project field values +""" +union ProjectV2ItemFieldValue = ProjectV2ItemFieldDateValue|ProjectV2ItemFieldIterationValue|ProjectV2ItemFieldLabelValue|ProjectV2ItemFieldMilestoneValue|ProjectV2ItemFieldNumberValue|ProjectV2ItemFieldPullRequestValue|ProjectV2ItemFieldRepositoryValue|ProjectV2ItemFieldReviewerValue|ProjectV2ItemFieldSingleSelectValue|ProjectV2ItemFieldTextValue|ProjectV2ItemFieldUserValue + +""" +Common fields across different project field value types +""" +interface ProjectV2ItemFieldValueCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the item. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The project field that contains this value. + """ + field: ProjectV2FieldConfiguration! + + """ + The Node ID of the ProjectV2ItemFieldValueCommon object + """ + id: ID! + + """ + The project item that contains this value. + """ + item: ProjectV2Item! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2ItemFieldValue. +""" +type ProjectV2ItemFieldValueConnection { + """ + A list of edges. + """ + edges: [ProjectV2ItemFieldValueEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2ItemFieldValue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2ItemFieldValueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2ItemFieldValue +} + +""" +Ordering options for project v2 item field value connections +""" +input ProjectV2ItemFieldValueOrder { + """ + The field to order the project v2 item field values by. + """ + field: ProjectV2ItemFieldValueOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which project v2 item field value connections can be ordered. +""" +enum ProjectV2ItemFieldValueOrderField { + """ + Order project v2 item field values by the their position in the project + """ + POSITION +} + +""" +Ordering options for project v2 item connections +""" +input ProjectV2ItemOrder { + """ + The field to order the project v2 items by. + """ + field: ProjectV2ItemOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which project v2 item connections can be ordered. +""" +enum ProjectV2ItemOrderField { + """ + Order project v2 items by the their position in the project + """ + POSITION +} + +""" +The type of a project item. +""" +enum ProjectV2ItemType { + """ + Issue + """ + ISSUE + + """ + Pull Request + """ + PULL_REQUEST + + """ + Draft Issue + """ + DRAFT_ISSUE + + """ + Redacted Item + """ + REDACTED +} + +""" +An iteration field inside a project. +""" +type ProjectV2IterationField implements Node & ProjectV2FieldCommon { + """ + Iteration configuration settings + """ + configuration: ProjectV2IterationFieldConfiguration! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectV2IterationField object + """ + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Iteration field configuration for a project. +""" +type ProjectV2IterationFieldConfiguration { + """ + The iteration's completed iterations + """ + completedIterations: [ProjectV2IterationFieldIteration!]! + + """ + The iteration's duration in days + """ + duration: Int! + + """ + The iteration's iterations + """ + iterations: [ProjectV2IterationFieldIteration!]! + + """ + The iteration's start day of the week + """ + startDay: Int! +} + +""" +Iteration field iteration settings for a project. +""" +type ProjectV2IterationFieldIteration { + """ + The iteration's duration in days + """ + duration: Int! + + """ + The iteration's ID. + """ + id: String! + + """ + The iteration's start date + """ + startDate: Date! + + """ + The iteration's title. + """ + title: String! + + """ + The iteration's html title. + """ + titleHTML: String! +} + +""" +Ways in which lists of projects can be ordered upon return. +""" +input ProjectV2Order { + """ + The field in which to order projects by. + """ + field: ProjectV2OrderField! + + """ + The direction in which to order projects by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which projects can be ordered. +""" +enum ProjectV2OrderField { + """ + The project's title + """ + TITLE + + """ + The project's number + """ + NUMBER + + """ + The project's date and time of update + """ + UPDATED_AT + + """ + The project's date and time of creation + """ + CREATED_AT +} + +""" +Represents an owner of a project. +""" +interface ProjectV2Owner { + """ + The Node ID of the ProjectV2Owner object + """ + id: ID! + + """ + Find a project by number. + """ + projectV2("The project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projectsV2("A project to search for under the the owner." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! +} + +""" +Recent projects for the owner. +""" +interface ProjectV2Recent { + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! +} + +""" +The possible roles of a collaborator on a project. +""" +enum ProjectV2Roles { + """ + The collaborator has no direct access to the project + """ + NONE + + """ + The collaborator can view the project + """ + READER + + """ + The collaborator can view and edit the project + """ + WRITER + + """ + The collaborator can view, edit, and maange the settings of the project + """ + ADMIN +} + +""" +A single select field inside a project. +""" +type ProjectV2SingleSelectField implements Node & ProjectV2FieldCommon { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The field's type. + """ + dataType: ProjectV2FieldType! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectV2SingleSelectField object + """ + id: ID! + + """ + The project field's name. + """ + name: String! + + """ + Options for the single select field + """ + options("Filter returned options to only those matching these names, case insensitive." names: [String!]): [ProjectV2SingleSelectFieldOption!]! + + """ + The project that contains this field. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +Single select field option for a configuration for a project. +""" +type ProjectV2SingleSelectFieldOption { + """ + The option's display color. + """ + color: ProjectV2SingleSelectFieldOptionColor! + + """ + The option's plain-text description. + """ + description: String! + + """ + The option's description, possibly containing HTML. + """ + descriptionHTML: String! + + """ + The option's ID. + """ + id: String! + + """ + The option's name. + """ + name: String! + + """ + The option's html name. + """ + nameHTML: String! +} + +""" +The display color of a single-select field option. +""" +enum ProjectV2SingleSelectFieldOptionColor { + """ + GRAY + """ + GRAY + + """ + BLUE + """ + BLUE + + """ + GREEN + """ + GREEN + + """ + YELLOW + """ + YELLOW + + """ + ORANGE + """ + ORANGE + + """ + RED + """ + RED + + """ + PINK + """ + PINK + + """ + PURPLE + """ + PURPLE +} + +""" +Represents a single select field option +""" +input ProjectV2SingleSelectFieldOptionInput { + """ + The name of the option + """ + name: String! + + """ + The display color of the option + """ + color: ProjectV2SingleSelectFieldOptionColor! + + """ + The description text of the option + """ + description: String! +} + +""" +Represents a sort by field and direction. +""" +type ProjectV2SortBy { + """ + The direction of the sorting. Possible values are ASC and DESC. + """ + direction: OrderDirection! + + """ + The field by which items are sorted. + """ + field: ProjectV2Field! +} + +""" +The connection type for ProjectV2SortBy. +""" +type ProjectV2SortByConnection { + """ + A list of edges. + """ + edges: [ProjectV2SortByEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2SortBy] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2SortByEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2SortBy +} + +""" +Represents a sort by field and direction. +""" +type ProjectV2SortByField { + """ + The direction of the sorting. Possible values are ASC and DESC. + """ + direction: OrderDirection! + + """ + The field by which items are sorted. + """ + field: ProjectV2FieldConfiguration! +} + +""" +The connection type for ProjectV2SortByField. +""" +type ProjectV2SortByFieldConnection { + """ + A list of edges. + """ + edges: [ProjectV2SortByFieldEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2SortByField] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2SortByFieldEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2SortByField +} + +""" +The possible states of a project v2. +""" +enum ProjectV2State { + """ + A project v2 that is still open + """ + OPEN + + """ + A project v2 that has been closed + """ + CLOSED +} + +""" +Ways in which project v2 status updates can be ordered. +""" +input ProjectV2StatusOrder { + """ + The field by which to order nodes. + """ + field: ProjectV2StatusUpdateOrderField! + + """ + The direction in which to order nodes. + """ + direction: OrderDirection! +} + +""" +A status update within a project. +""" +type ProjectV2StatusUpdate implements Node { + """ + The body of the status update. + """ + body: String + + """ + The body of the status update rendered to HTML. + """ + bodyHTML: HTML + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created the status update. + """ + creator: Actor + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ProjectV2StatusUpdate object + """ + id: ID! + + """ + The project that contains this status update. + """ + project: ProjectV2! + + """ + The start date of the status update. + """ + startDate: Date + + """ + The status of the status update. + """ + status: ProjectV2StatusUpdateStatus + + """ + The target date of the status update. + """ + targetDate: Date + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2StatusUpdate. +""" +type ProjectV2StatusUpdateConnection { + """ + A list of edges. + """ + edges: [ProjectV2StatusUpdateEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2StatusUpdate] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2StatusUpdateEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2StatusUpdate +} + +""" +Properties by which project v2 status updates can be ordered. +""" +enum ProjectV2StatusUpdateOrderField { + """ + Allows chronological ordering of project v2 status updates. + """ + CREATED_AT +} + +""" +The possible statuses of a project v2. +""" +enum ProjectV2StatusUpdateStatus { + """ + A project v2 that is inactive. + """ + INACTIVE + + """ + A project v2 that is on track with no risks. + """ + ON_TRACK + + """ + A project v2 that is at risk and encountering some challenges. + """ + AT_RISK + + """ + A project v2 that is off track and needs attention. + """ + OFF_TRACK + + """ + A project v2 that is complete. + """ + COMPLETE +} + +""" +A view within a ProjectV2. +""" +type ProjectV2View implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The view's visible fields. + """ + fields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConfigurationConnection + + """ + The project view's filter. + """ + filter: String + + """ + The view's group-by field. + """ + groupBy("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConnection @deprecated(reason: "The `ProjectV2View#order_by` API is deprecated in favour of the more capable `ProjectV2View#group_by_field` API. Check out the `ProjectV2View#group_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC.") + + """ + The view's group-by field. + """ + groupByFields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConfigurationConnection + + """ + The Node ID of the ProjectV2View object + """ + id: ID! + + """ + The project view's layout. + """ + layout: ProjectV2ViewLayout! + + """ + The project view's name. + """ + name: String! + + """ + The project view's number. + """ + number: Int! + + """ + The project that contains this view. + """ + project: ProjectV2! + + """ + The view's sort-by config. + """ + sortBy("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2SortByConnection @deprecated(reason: "The `ProjectV2View#sort_by` API is deprecated in favour of the more capable `ProjectV2View#sort_by_fields` API. Check out the `ProjectV2View#sort_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC.") + + """ + The view's sort-by config. + """ + sortByFields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2SortByFieldConnection + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The view's vertical-group-by field. + """ + verticalGroupBy("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConnection @deprecated(reason: "The `ProjectV2View#vertical_group_by` API is deprecated in favour of the more capable `ProjectV2View#vertical_group_by_fields` API. Check out the `ProjectV2View#vertical_group_by_fields` API as an example for the more capable alternative. Removal on 2023-04-01 UTC.") + + """ + The view's vertical-group-by field. + """ + verticalGroupByFields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConfigurationConnection + + """ + The view's visible fields. + """ + visibleFields("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the project v2 fields returned from the connection." orderBy: ProjectV2FieldOrder = { + field: POSITION + direction: ASC + } + ): ProjectV2FieldConnection @deprecated(reason: "The `ProjectV2View#visibleFields` API is deprecated in favour of the more capable `ProjectV2View#fields` API. Check out the `ProjectV2View#fields` API as an example for the more capable alternative. Removal on 2023-01-01 UTC.") +} + +""" +The connection type for ProjectV2View. +""" +type ProjectV2ViewConnection { + """ + A list of edges. + """ + edges: [ProjectV2ViewEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2View] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2ViewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2View +} + +""" +The layout of a project v2 view. +""" +enum ProjectV2ViewLayout { + """ + Board layout + """ + BOARD_LAYOUT + + """ + Table layout + """ + TABLE_LAYOUT + + """ + Roadmap layout + """ + ROADMAP_LAYOUT +} + +""" +Ordering options for project v2 view connections +""" +input ProjectV2ViewOrder { + """ + The field to order the project v2 views by. + """ + field: ProjectV2ViewOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which project v2 view connections can be ordered. +""" +enum ProjectV2ViewOrderField { + """ + Order project v2 views by position + """ + POSITION + + """ + Order project v2 views by creation time + """ + CREATED_AT + + """ + Order project v2 views by name + """ + NAME +} + +""" +A workflow inside a project. +""" +type ProjectV2Workflow implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Whether the workflow is enabled. + """ + enabled: Boolean! + + """ + The Node ID of the ProjectV2Workflow object + """ + id: ID! + + """ + The name of the workflow. + """ + name: String! + + """ + The number of the workflow. + """ + number: Int! + + """ + The project that contains this workflow. + """ + project: ProjectV2! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for ProjectV2Workflow. +""" +type ProjectV2WorkflowConnection { + """ + A list of edges. + """ + edges: [ProjectV2WorkflowEdge] + + """ + A list of nodes. + """ + nodes: [ProjectV2Workflow] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ProjectV2WorkflowEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ProjectV2Workflow +} + +""" +Ordering options for project v2 workflows connections +""" +input ProjectV2WorkflowOrder { + """ + The field to order the project v2 workflows by. + """ + field: ProjectV2WorkflowsOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which project workflows can be ordered. +""" +enum ProjectV2WorkflowsOrderField { + """ + The name of the workflow + """ + NAME + + """ + The number of the workflow + """ + NUMBER + + """ + The date and time of the workflow update + """ + UPDATED_AT + + """ + The date and time of the workflow creation + """ + CREATED_AT +} + +""" +A property that must match +""" +type PropertyTargetDefinition { + """ + The name of the property + """ + name: String! + + """ + The values to match for + """ + propertyValues: [String!]! +} + +""" +A property that must match +""" +input PropertyTargetDefinitionInput { + """ + The name of the property + """ + name: String! + + """ + The values to match for + """ + propertyValues: [String!]! +} + +""" +A user's public key. +""" +type PublicKey implements Node { + """ + The last time this authorization was used to perform an action. Values will be null for keys not owned by the user. + """ + accessedAt: DateTime + + """ + Identifies the date and time when the key was created. Keys created before March 5th, 2014 have inaccurate values. Values will be null for keys not owned by the user. + """ + createdAt: DateTime + + """ + The fingerprint for this PublicKey. + """ + fingerprint: String! + + """ + The Node ID of the PublicKey object + """ + id: ID! + + """ + Whether this PublicKey is read-only or not. Values will be null for keys not owned by the user. + """ + isReadOnly: Boolean + + """ + The public key string. + """ + key: String! + + """ + Identifies the date and time when the key was updated. Keys created before March 5th, 2014 may have inaccurate values. Values will be null for keys not owned by the user. + """ + updatedAt: DateTime +} + +""" +The connection type for PublicKey. +""" +type PublicKeyConnection { + """ + A list of edges. + """ + edges: [PublicKeyEdge] + + """ + A list of nodes. + """ + nodes: [PublicKey] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PublicKeyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PublicKey +} + +""" +Autogenerated input type of PublishSponsorsTier +""" +input PublishSponsorsTierInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the draft tier to publish. + """ + tierId: ID! +} + +""" +Autogenerated return type of PublishSponsorsTier. +""" +type PublishSponsorsTierPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The tier that was published. + """ + sponsorsTier: SponsorsTier +} + +""" +A repository pull request. +""" +type PullRequest implements Assignable & Closable & Comment & Labelable & Lockable & Node & ProjectV2Owner & Reactable & RepositoryNode & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + Reason that the conversation was locked. + """ + activeLockReason: LockReason + + """ + The number of additions in this pull request. + """ + additions: Int! + + """ + A list of Users assigned to this object. + """ + assignees("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Returns the auto-merge request object if one exists for this pull request. + """ + autoMergeRequest: AutoMergeRequest + + """ + Identifies the base Ref associated with the pull request. + """ + baseRef: Ref + + """ + Identifies the name of the base Ref associated with the pull request, even if the ref has been deleted. + """ + baseRefName: String! + + """ + Identifies the oid of the base ref associated with the pull request, even if the ref has been deleted. + """ + baseRefOid: GitObjectID! + + """ + The repository associated with this pull request's base Ref. + """ + baseRepository: Repository + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Whether or not the pull request is rebaseable. + """ + canBeRebased: Boolean! + + """ + The number of changed files in this pull request. + """ + changedFiles: Int! + + """ + The HTTP path for the checks of this pull request. + """ + checksResourcePath: URI! + + """ + The HTTP URL for the checks of this pull request. + """ + checksUrl: URI! + + """ + `true` if the pull request is closed + """ + closed: Boolean! + + """ + Identifies the date and time when the object was closed. + """ + closedAt: DateTime + + """ + List of issues that were may be closed by this pull request + """ + closingIssuesReferences("Return only manually linked Issues" userLinkedOnly: Boolean = false, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for issues returned from the connection" orderBy: IssueOrder): IssueConnection + + """ + A list of comments associated with the pull request. + """ + comments("Ordering options for issue comments returned from the connection." orderBy: IssueCommentOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueCommentConnection! + + """ + A list of commits present in this pull request's head branch not present in the base branch. + """ + commits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestCommitConnection! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int @deprecated(reason: "`databaseId` will be removed because it does not support 64-bit signed integer identifiers. Use `fullDatabaseId` instead. Removal on 2024-07-01 UTC.") + + """ + The number of deletions in this pull request. + """ + deletions: Int! + + """ + The actor who edited this pull request's body. + """ + editor: Actor + + """ + Lists the files changed within this pull request. + """ + files("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestChangedFileConnection + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + Identifies the head Ref associated with the pull request. + """ + headRef: Ref + + """ + Identifies the name of the head Ref associated with the pull request, even if the ref has been deleted. + """ + headRefName: String! + + """ + Identifies the oid of the head ref associated with the pull request, even if the ref has been deleted. + """ + headRefOid: GitObjectID! + + """ + The repository associated with this pull request's head Ref. + """ + headRepository: Repository + + """ + The owner of the repository associated with this pull request's head Ref. + """ + headRepositoryOwner: RepositoryOwner + + """ + The hovercard information for this issue + """ + hovercard("Whether or not to include notification contexts" includeNotificationContexts: Boolean = true): Hovercard! + + """ + The Node ID of the PullRequest object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The head and base repositories are different. + """ + isCrossRepository: Boolean! + + """ + Identifies if the pull request is a draft. + """ + isDraft: Boolean! + + """ + Indicates whether the pull request is in a merge queue + """ + isInMergeQueue: Boolean! + + """ + Indicates whether the pull request's base ref has a merge queue enabled. + """ + isMergeQueueEnabled: Boolean! + + """ + Is this pull request read by the viewer + """ + isReadByViewer: Boolean + + """ + A list of labels associated with the object. + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): LabelConnection + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + A list of latest reviews per user associated with the pull request. + """ + latestOpinionatedReviews("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Only return reviews from user who have write access to the repository" writersOnly: Boolean = false): PullRequestReviewConnection + + """ + A list of latest reviews per user associated with the pull request that are not also pending review. + """ + latestReviews("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestReviewConnection + + """ + `true` if the pull request is locked + """ + locked: Boolean! + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean! + + """ + The commit that was created when this pull request was merged. + """ + mergeCommit: Commit + + """ + The merge queue for the pull request's base branch + """ + mergeQueue: MergeQueue + + """ + The merge queue entry of the pull request in the base branch's merge queue + """ + mergeQueueEntry: MergeQueueEntry + + """ + Detailed information about the current pull request merge state status. + """ + mergeStateStatus: MergeStateStatus! + + """ + Whether or not the pull request can be merged based on the existence of merge conflicts. + """ + mergeable: MergeableState! + + """ + Whether or not the pull request was merged. + """ + merged: Boolean! + + """ + The date and time that the pull request was merged. + """ + mergedAt: DateTime + + """ + The actor who merged the pull request. + """ + mergedBy: Actor + + """ + Identifies the milestone associated with the pull request. + """ + milestone: Milestone + + """ + Identifies the pull request number. + """ + number: Int! + + """ + A list of Users that are participating in the Pull Request conversation. + """ + participants("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + The permalink to the pull request. + """ + permalink: URI! + + """ + The commit that GitHub automatically generated to test if this pull request could be merged. This field will not return a value if the pull request is merged, or if the test merge commit is still being generated. See the `mergeable` field for more details on the mergeability of the pull request. + """ + potentialMergeCommit: Commit + + """ + List of project cards associated with this pull request. + """ + projectCards("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A list of archived states to filter the cards by" archivedStates: [ProjectCardArchivedState] = [ARCHIVED,NOT_ARCHIVED]): ProjectCardConnection! + + """ + List of project items associated with this pull request. + """ + projectItems("Include archived items." includeArchived: Boolean = true, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2ItemConnection! + + """ + Find a project by number. + """ + projectV2("The project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projectsV2("A project to search for under the the owner." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path for this pull request. + """ + resourcePath: URI! + + """ + The HTTP path for reverting this pull request. + """ + revertResourcePath: URI! + + """ + The HTTP URL for reverting this pull request. + """ + revertUrl: URI! + + """ + The current status of this pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision + + """ + A list of review requests associated with the pull request. + """ + reviewRequests("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ReviewRequestConnection + + """ + The list of all review threads for this pull request. + """ + reviewThreads("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestReviewThreadConnection! + + """ + A list of reviews associated with the pull request. + """ + reviews("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A list of states to filter the reviews." states: [PullRequestReviewState!], "Filter by author of the review." author: String): PullRequestReviewConnection + + """ + Identifies the state of the pull request. + """ + state: PullRequestState! + + """ + Check and Status rollup information for the PR's head ref. + """ + statusCheckRollup: StatusCheckRollup + + """ + A list of reviewer suggestions based on commit history and past review comments. + """ + suggestedReviewers: [SuggestedReviewer]! + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timeline("Allows filtering timeline events by a `since` timestamp." since: DateTime, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestTimelineConnection! @deprecated(reason: "`timeline` will be removed Use PullRequest.timelineItems instead. Removal on 2020-10-01 UTC.") + + """ + A list of events, comments, commits, etc. associated with the pull request. + """ + timelineItems("Filter timeline items by a `since` timestamp." since: DateTime, "Skips the first _n_ elements in the list." skip: Int, "Filter timeline items by type." itemTypes: [PullRequestTimelineItemsItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestTimelineItemsConnection! + + """ + Identifies the pull request title. + """ + title: String! + + """ + Identifies the pull request title rendered to HTML. + """ + titleHTML: HTML! + + """ + Returns a count of how many comments this pull request has received. + """ + totalCommentsCount: Int + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this pull request. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Whether or not the viewer can apply suggestion. + """ + viewerCanApplySuggestion: Boolean! + + """ + Indicates if the object can be closed by the viewer. + """ + viewerCanClose: Boolean! + + """ + Check if the viewer can restore the deleted head ref. + """ + viewerCanDeleteHeadRef: Boolean! + + """ + Whether or not the viewer can disable auto-merge + """ + viewerCanDisableAutoMerge: Boolean! + + """ + Can the viewer edit files within this pull request. + """ + viewerCanEditFiles: Boolean! + + """ + Whether or not the viewer can enable auto-merge + """ + viewerCanEnableAutoMerge: Boolean! + + """ + Indicates whether the viewer can bypass branch protections and merge the pull request immediately + """ + viewerCanMergeAsAdmin: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Indicates if the object can be reopened by the viewer. + """ + viewerCanReopen: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Whether or not the viewer can update the head ref of this PR, by merging or rebasing the base ref. + If the head ref is up to date or unable to be updated by this user, this will return false. + """ + viewerCanUpdateBranch: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + The latest review given from the viewer. + """ + viewerLatestReview: PullRequestReview + + """ + The person who has requested the viewer for review on this pull request. + """ + viewerLatestReviewRequest: ReviewRequest + + """ + The merge body text for the viewer and method. + """ + viewerMergeBodyText("The merge method for the message." mergeType: PullRequestMergeMethod): String! + + """ + The merge headline text for the viewer and method. + """ + viewerMergeHeadlineText("The merge method for the message." mergeType: PullRequestMergeMethod): String! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +The possible methods for updating a pull request's head branch with the base branch. +""" +enum PullRequestBranchUpdateMethod { + """ + Update branch via merge + """ + MERGE + + """ + Update branch via rebase + """ + REBASE +} + +""" +A file changed in a pull request. +""" +type PullRequestChangedFile { + """ + The number of additions to the file. + """ + additions: Int! + + """ + How the file was changed in this PullRequest + """ + changeType: PatchStatus! + + """ + The number of deletions to the file. + """ + deletions: Int! + + """ + The path of the file. + """ + path: String! + + """ + The state of the file for the viewer. + """ + viewerViewedState: FileViewedState! +} + +""" +The connection type for PullRequestChangedFile. +""" +type PullRequestChangedFileConnection { + """ + A list of edges. + """ + edges: [PullRequestChangedFileEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestChangedFile] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestChangedFileEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestChangedFile +} + +""" +Represents a Git commit part of a pull request. +""" +type PullRequestCommit implements Node & UniformResourceLocatable { + """ + The Git commit object + """ + commit: Commit! + + """ + The Node ID of the PullRequestCommit object + """ + id: ID! + + """ + The pull request this commit belongs to + """ + pullRequest: PullRequest! + + """ + The HTTP path for this pull request commit + """ + resourcePath: URI! + + """ + The HTTP URL for this pull request commit + """ + url: URI! +} + +""" +Represents a commit comment thread part of a pull request. +""" +type PullRequestCommitCommentThread implements Node & RepositoryNode { + """ + The comments that exist in this thread. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitCommentConnection! + + """ + The commit the comments were made on. + """ + commit: Commit! + + """ + The Node ID of the PullRequestCommitCommentThread object + """ + id: ID! + + """ + The file the comments were made on. + """ + path: String + + """ + The position in the diff for the commit that the comment was made on. + """ + position: Int + + """ + The pull request this commit comment thread belongs to + """ + pullRequest: PullRequest! + + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +The connection type for PullRequestCommit. +""" +type PullRequestCommitConnection { + """ + A list of edges. + """ + edges: [PullRequestCommitEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestCommit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestCommitEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestCommit +} + +""" +The connection type for PullRequest. +""" +type PullRequestConnection { + """ + A list of edges. + """ + edges: [PullRequestEdge] + + """ + A list of nodes. + """ + nodes: [PullRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull requests opened by a user within one repository. +""" +type PullRequestContributionsByRepository { + """ + The pull request contributions. + """ + contributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedPullRequestContributionConnection! + + """ + The repository in which the pull requests were opened. + """ + repository: Repository! +} + +""" +An edge in a connection. +""" +type PullRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequest +} + +""" +Represents available types of methods to use when merging a pull request. +""" +enum PullRequestMergeMethod { + """ + Add all commits from the head branch to the base branch with a merge commit. + """ + MERGE + + """ + Combine all commits from the head branch into a single commit in the base branch. + """ + SQUASH + + """ + Add all commits from the head branch onto the base branch individually. + """ + REBASE +} + +""" +Ways in which lists of issues can be ordered upon return. +""" +input PullRequestOrder { + """ + The field in which to order pull requests by. + """ + field: PullRequestOrderField! + + """ + The direction in which to order pull requests by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which pull_requests connections can be ordered. +""" +enum PullRequestOrderField { + """ + Order pull_requests by creation time + """ + CREATED_AT + + """ + Order pull_requests by update time + """ + UPDATED_AT +} + +""" +Require all commits be made to a non-target branch and submitted via a pull request before they can be merged. +""" +type PullRequestParameters { + """ + New, reviewable commits pushed will dismiss previous pull request review approvals. + """ + dismissStaleReviewsOnPush: Boolean! + + """ + Require an approving review in pull requests that modify files that have a designated code owner. + """ + requireCodeOwnerReview: Boolean! + + """ + Whether the most recent reviewable push must be approved by someone other than the person who pushed it. + """ + requireLastPushApproval: Boolean! + + """ + The number of approving reviews that are required before a pull request can be merged. + """ + requiredApprovingReviewCount: Int! + + """ + All conversations on code must be resolved before a pull request can be merged. + """ + requiredReviewThreadResolution: Boolean! +} + +""" +Require all commits be made to a non-target branch and submitted via a pull request before they can be merged. +""" +input PullRequestParametersInput { + """ + New, reviewable commits pushed will dismiss previous pull request review approvals. + """ + dismissStaleReviewsOnPush: Boolean! + + """ + Require an approving review in pull requests that modify files that have a designated code owner. + """ + requireCodeOwnerReview: Boolean! + + """ + Whether the most recent reviewable push must be approved by someone other than the person who pushed it. + """ + requireLastPushApproval: Boolean! + + """ + The number of approving reviews that are required before a pull request can be merged. + """ + requiredApprovingReviewCount: Int! + + """ + All conversations on code must be resolved before a pull request can be merged. + """ + requiredReviewThreadResolution: Boolean! +} + +""" +A review object for a given pull request. +""" +type PullRequestReview implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + Indicates whether the author of this review has push access to the repository. + """ + authorCanPushToRepository: Boolean! + + """ + Identifies the pull request review body. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body of this review rendered as plain text. + """ + bodyText: String! + + """ + A list of review comments for the current pull request review. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestReviewCommentConnection! + + """ + Identifies the commit associated with this pull request review. + """ + commit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int @deprecated(reason: "`databaseId` will be removed because it does not support 64-bit signed integer identifiers. Use `fullDatabaseId` instead. Removal on 2024-07-01 UTC.") + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The Node ID of the PullRequestReview object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + A list of teams that this review was made on behalf of. + """ + onBehalfOf("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): TeamConnection! + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + Identifies the pull request associated with this pull request review. + """ + pullRequest: PullRequest! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this PullRequestReview. + """ + resourcePath: URI! + + """ + Identifies the current state of the pull request review. + """ + state: PullRequestReviewState! + + """ + Identifies when the Pull Request Review was submitted + """ + submittedAt: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this PullRequestReview. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +A review comment associated with a given repository pull request. +""" +type PullRequestReviewComment implements Comment & Deletable & Minimizable & Node & Reactable & RepositoryNode & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the subject of the comment. + """ + authorAssociation: CommentAuthorAssociation! + + """ + The comment body of this review comment. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The comment body of this review comment rendered as plain text. + """ + bodyText: String! + + """ + Identifies the commit associated with the comment. + """ + commit: Commit + + """ + Identifies when the comment was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int @deprecated(reason: "`databaseId` will be removed because it does not support 64-bit signed integer identifiers. Use `fullDatabaseId` instead. Removal on 2024-07-01 UTC.") + + """ + The diff hunk to which the comment applies. + """ + diffHunk: String! + + """ + Identifies when the comment was created in a draft state. + """ + draftedAt: DateTime! + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + Identifies the primary key from the database as a BigInt. + """ + fullDatabaseId: BigInt + + """ + The Node ID of the PullRequestReviewComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Returns whether or not a comment has been minimized. + """ + isMinimized: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + The end line number on the file to which the comment applies + """ + line: Int + + """ + Returns why the comment was minimized. One of `abuse`, `off-topic`, `outdated`, `resolved`, `duplicate` and `spam`. Note that the case and formatting of these values differs from the inputs to the `MinimizeComment` mutation. + """ + minimizedReason: String + + """ + Identifies the original commit associated with the comment. + """ + originalCommit: Commit + + """ + The end line number on the file to which the comment applied when it was first created + """ + originalLine: Int + + """ + The original line index in the diff to which the comment applies. + """ + originalPosition: Int! @deprecated(reason: "We are phasing out diff-relative positioning for PR comments Removal on 2023-10-01 UTC.") + + """ + The start line number on the file to which the comment applied when it was first created + """ + originalStartLine: Int + + """ + Identifies when the comment body is outdated + """ + outdated: Boolean! + + """ + The path to which the comment applies. + """ + path: String! + + """ + The line index in the diff to which the comment applies. + """ + position: Int @deprecated(reason: "We are phasing out diff-relative positioning for PR comments Use the `line` and `startLine` fields instead, which are file line numbers instead of diff line numbers Removal on 2023-10-01 UTC.") + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + The pull request associated with this review comment. + """ + pullRequest: PullRequest! + + """ + The pull request review associated with this review comment. + """ + pullRequestReview: PullRequestReview + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The comment this is a reply to. + """ + replyTo: PullRequestReviewComment + + """ + The repository associated with this node. + """ + repository: Repository! + + """ + The HTTP path permalink for this review comment. + """ + resourcePath: URI! + + """ + The start line number on the file to which the comment applies + """ + startLine: Int + + """ + Identifies the state of the comment. + """ + state: PullRequestReviewCommentState! + + """ + The level at which the comments in the corresponding thread are targeted, can be a diff line or a file + """ + subjectType: PullRequestReviewThreadSubjectType! + + """ + Identifies when the comment was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL permalink for this review comment. + """ + url: URI! + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Check if the current viewer can minimize this object. + """ + viewerCanMinimize: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for PullRequestReviewComment. +""" +type PullRequestReviewCommentConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewCommentEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewComment +} + +""" +The possible states of a pull request review comment. +""" +enum PullRequestReviewCommentState { + """ + A comment that is part of a pending review + """ + PENDING + + """ + A comment that is part of a submitted review + """ + SUBMITTED +} + +""" +The connection type for PullRequestReview. +""" +type PullRequestReviewConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReview] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +This aggregates pull request reviews made by a user within one repository. +""" +type PullRequestReviewContributionsByRepository { + """ + The pull request review contributions. + """ + contributions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for contributions returned from the connection." orderBy: ContributionOrder = { + direction: DESC + } + ): CreatedPullRequestReviewContributionConnection! + + """ + The repository in which the pull request reviews were made. + """ + repository: Repository! +} + +""" +The review status of a pull request. +""" +enum PullRequestReviewDecision { + """ + Changes have been requested on the pull request. + """ + CHANGES_REQUESTED + + """ + The pull request has received an approving review. + """ + APPROVED + + """ + A review is required before the pull request can be merged. + """ + REVIEW_REQUIRED +} + +""" +An edge in a connection. +""" +type PullRequestReviewEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReview +} + +""" +The possible events to perform on a pull request review. +""" +enum PullRequestReviewEvent { + """ + Submit general feedback without explicit approval. + """ + COMMENT + + """ + Submit feedback and approve merging these changes. + """ + APPROVE + + """ + Submit feedback that must be addressed before merging. + """ + REQUEST_CHANGES + + """ + Dismiss review so it now longer effects merging. + """ + DISMISS +} + +""" +The possible states of a pull request review. +""" +enum PullRequestReviewState { + """ + A review that has not yet been submitted. + """ + PENDING + + """ + An informational review. + """ + COMMENTED + + """ + A review allowing the pull request to merge. + """ + APPROVED + + """ + A review blocking the pull request from merging. + """ + CHANGES_REQUESTED + + """ + A review that has been dismissed. + """ + DISMISSED +} + +""" +A threaded list of comments for a given pull request. +""" +type PullRequestReviewThread implements Node { + """ + A list of pull request comments associated with the thread. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Skips the first _n_ elements in the list." skip: Int): PullRequestReviewCommentConnection! + + """ + The side of the diff on which this thread was placed. + """ + diffSide: DiffSide! + + """ + The Node ID of the PullRequestReviewThread object + """ + id: ID! + + """ + Whether or not the thread has been collapsed (resolved) + """ + isCollapsed: Boolean! + + """ + Indicates whether this thread was outdated by newer changes. + """ + isOutdated: Boolean! + + """ + Whether this thread has been resolved + """ + isResolved: Boolean! + + """ + The line in the file to which this thread refers + """ + line: Int + + """ + The original line in the file to which this thread refers. + """ + originalLine: Int + + """ + The original start line in the file to which this thread refers (multi-line only). + """ + originalStartLine: Int + + """ + Identifies the file path of this thread. + """ + path: String! + + """ + Identifies the pull request associated with this thread. + """ + pullRequest: PullRequest! + + """ + Identifies the repository associated with this thread. + """ + repository: Repository! + + """ + The user who resolved this thread + """ + resolvedBy: User + + """ + The side of the diff that the first line of the thread starts on (multi-line only) + """ + startDiffSide: DiffSide + + """ + The start line in the file to which this thread refers (multi-line only) + """ + startLine: Int + + """ + The level at which the comments in the corresponding thread are targeted, can be a diff line or a file + """ + subjectType: PullRequestReviewThreadSubjectType! + + """ + Indicates whether the current viewer can reply to this thread. + """ + viewerCanReply: Boolean! + + """ + Whether or not the viewer can resolve this thread + """ + viewerCanResolve: Boolean! + + """ + Whether or not the viewer can unresolve this thread + """ + viewerCanUnresolve: Boolean! +} + +""" +Review comment threads for a pull request review. +""" +type PullRequestReviewThreadConnection { + """ + A list of edges. + """ + edges: [PullRequestReviewThreadEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestReviewThread] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PullRequestReviewThreadEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestReviewThread +} + +""" +The possible subject types of a pull request review comment. +""" +enum PullRequestReviewThreadSubjectType { + """ + A comment that has been made against the line of a pull request + """ + LINE + + """ + A comment that has been made against the file of a pull request + """ + FILE +} + +""" +Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. +""" +type PullRequestRevisionMarker { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The last commit the viewer has seen. + """ + lastSeenCommit: Commit! + + """ + The pull request to which the marker belongs. + """ + pullRequest: PullRequest! +} + +""" +The possible states of a pull request. +""" +enum PullRequestState { + """ + A pull request that is still open. + """ + OPEN + + """ + A pull request that has been closed without being merged. + """ + CLOSED + + """ + A pull request that has been closed by being merged. + """ + MERGED +} + +""" +A repository pull request template. +""" +type PullRequestTemplate { + """ + The body of the template + """ + body: String + + """ + The filename of the template + """ + filename: String + + """ + The repository the template belongs to + """ + repository: Repository! +} + +""" +A threaded list of comments for a given pull request. +""" +type PullRequestThread implements Node { + """ + A list of pull request comments associated with the thread. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Skips the first _n_ elements in the list." skip: Int): PullRequestReviewCommentConnection! + + """ + The side of the diff on which this thread was placed. + """ + diffSide: DiffSide! + + """ + The Node ID of the PullRequestThread object + """ + id: ID! + + """ + Whether or not the thread has been collapsed (resolved) + """ + isCollapsed: Boolean! + + """ + Indicates whether this thread was outdated by newer changes. + """ + isOutdated: Boolean! + + """ + Whether this thread has been resolved + """ + isResolved: Boolean! + + """ + The line in the file to which this thread refers + """ + line: Int + + """ + Identifies the file path of this thread. + """ + path: String! + + """ + Identifies the pull request associated with this thread. + """ + pullRequest: PullRequest! + + """ + Identifies the repository associated with this thread. + """ + repository: Repository! + + """ + The user who resolved this thread + """ + resolvedBy: User + + """ + The side of the diff that the first line of the thread starts on (multi-line only) + """ + startDiffSide: DiffSide + + """ + The line of the first file diff in the thread. + """ + startLine: Int + + """ + The level at which the comments in the corresponding thread are targeted, can be a diff line or a file + """ + subjectType: PullRequestReviewThreadSubjectType! + + """ + Indicates whether the current viewer can reply to this thread. + """ + viewerCanReply: Boolean! + + """ + Whether or not the viewer can resolve this thread + """ + viewerCanResolve: Boolean! + + """ + Whether or not the viewer can unresolve this thread + """ + viewerCanUnresolve: Boolean! +} + +""" +The connection type for PullRequestTimelineItem. +""" +type PullRequestTimelineConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemEdge] + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItem = AssignedEvent|BaseRefDeletedEvent|BaseRefForcePushedEvent|ClosedEvent|Commit|CommitCommentThread|CrossReferencedEvent|DemilestonedEvent|DeployedEvent|DeploymentEnvironmentChangedEvent|HeadRefDeletedEvent|HeadRefForcePushedEvent|HeadRefRestoredEvent|IssueComment|LabeledEvent|LockedEvent|MergedEvent|MilestonedEvent|PullRequestReview|PullRequestReviewComment|PullRequestReviewThread|ReferencedEvent|RenamedTitleEvent|ReopenedEvent|ReviewDismissedEvent|ReviewRequestRemovedEvent|ReviewRequestedEvent|SubscribedEvent|UnassignedEvent|UnlabeledEvent|UnlockedEvent|UnsubscribedEvent|UserBlockedEvent + +""" +An edge in a connection. +""" +type PullRequestTimelineItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItem +} + +""" +An item in a pull request timeline +""" +union PullRequestTimelineItems = AddedToMergeQueueEvent|AddedToProjectEvent|AssignedEvent|AutoMergeDisabledEvent|AutoMergeEnabledEvent|AutoRebaseEnabledEvent|AutoSquashEnabledEvent|AutomaticBaseChangeFailedEvent|AutomaticBaseChangeSucceededEvent|BaseRefChangedEvent|BaseRefDeletedEvent|BaseRefForcePushedEvent|ClosedEvent|CommentDeletedEvent|ConnectedEvent|ConvertToDraftEvent|ConvertedNoteToIssueEvent|ConvertedToDiscussionEvent|CrossReferencedEvent|DemilestonedEvent|DeployedEvent|DeploymentEnvironmentChangedEvent|DisconnectedEvent|HeadRefDeletedEvent|HeadRefForcePushedEvent|HeadRefRestoredEvent|IssueComment|LabeledEvent|LockedEvent|MarkedAsDuplicateEvent|MentionedEvent|MergedEvent|MilestonedEvent|MovedColumnsInProjectEvent|PinnedEvent|PullRequestCommit|PullRequestCommitCommentThread|PullRequestReview|PullRequestReviewThread|PullRequestRevisionMarker|ReadyForReviewEvent|ReferencedEvent|RemovedFromMergeQueueEvent|RemovedFromProjectEvent|RenamedTitleEvent|ReopenedEvent|ReviewDismissedEvent|ReviewRequestRemovedEvent|ReviewRequestedEvent|SubscribedEvent|TransferredEvent|UnassignedEvent|UnlabeledEvent|UnlockedEvent|UnmarkedAsDuplicateEvent|UnpinnedEvent|UnsubscribedEvent|UserBlockedEvent + +""" +The connection type for PullRequestTimelineItems. +""" +type PullRequestTimelineItemsConnection { + """ + A list of edges. + """ + edges: [PullRequestTimelineItemsEdge] + + """ + Identifies the count of items after applying `before` and `after` filters. + """ + filteredCount: Int! + + """ + A list of nodes. + """ + nodes: [PullRequestTimelineItems] + + """ + Identifies the count of items after applying `before`/`after` filters and `first`/`last`/`skip` slicing. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Identifies the date and time when the timeline was last updated. + """ + updatedAt: DateTime! +} + +""" +An edge in a connection. +""" +type PullRequestTimelineItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PullRequestTimelineItems +} + +""" +The possible item types found in a timeline. +""" +enum PullRequestTimelineItemsItemType { + """ + Represents a Git commit part of a pull request. + """ + PULL_REQUEST_COMMIT + + """ + Represents a commit comment thread part of a pull request. + """ + PULL_REQUEST_COMMIT_COMMENT_THREAD + + """ + A review object for a given pull request. + """ + PULL_REQUEST_REVIEW + + """ + A threaded list of comments for a given pull request. + """ + PULL_REQUEST_REVIEW_THREAD + + """ + Represents the latest point in the pull request timeline for which the viewer has seen the pull request's commits. + """ + PULL_REQUEST_REVISION_MARKER + + """ + Represents a 'automatic_base_change_failed' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_FAILED_EVENT + + """ + Represents a 'automatic_base_change_succeeded' event on a given pull request. + """ + AUTOMATIC_BASE_CHANGE_SUCCEEDED_EVENT + + """ + Represents a 'auto_merge_disabled' event on a given pull request. + """ + AUTO_MERGE_DISABLED_EVENT + + """ + Represents a 'auto_merge_enabled' event on a given pull request. + """ + AUTO_MERGE_ENABLED_EVENT + + """ + Represents a 'auto_rebase_enabled' event on a given pull request. + """ + AUTO_REBASE_ENABLED_EVENT + + """ + Represents a 'auto_squash_enabled' event on a given pull request. + """ + AUTO_SQUASH_ENABLED_EVENT + + """ + Represents a 'base_ref_changed' event on a given issue or pull request. + """ + BASE_REF_CHANGED_EVENT + + """ + Represents a 'base_ref_force_pushed' event on a given pull request. + """ + BASE_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'base_ref_deleted' event on a given pull request. + """ + BASE_REF_DELETED_EVENT + + """ + Represents a 'deployed' event on a given pull request. + """ + DEPLOYED_EVENT + + """ + Represents a 'deployment_environment_changed' event on a given pull request. + """ + DEPLOYMENT_ENVIRONMENT_CHANGED_EVENT + + """ + Represents a 'head_ref_deleted' event on a given pull request. + """ + HEAD_REF_DELETED_EVENT + + """ + Represents a 'head_ref_force_pushed' event on a given pull request. + """ + HEAD_REF_FORCE_PUSHED_EVENT + + """ + Represents a 'head_ref_restored' event on a given pull request. + """ + HEAD_REF_RESTORED_EVENT + + """ + Represents a 'merged' event on a given pull request. + """ + MERGED_EVENT + + """ + Represents a 'review_dismissed' event on a given issue or pull request. + """ + REVIEW_DISMISSED_EVENT + + """ + Represents an 'review_requested' event on a given pull request. + """ + REVIEW_REQUESTED_EVENT + + """ + Represents an 'review_request_removed' event on a given pull request. + """ + REVIEW_REQUEST_REMOVED_EVENT + + """ + Represents a 'ready_for_review' event on a given pull request. + """ + READY_FOR_REVIEW_EVENT + + """ + Represents a 'convert_to_draft' event on a given pull request. + """ + CONVERT_TO_DRAFT_EVENT + + """ + Represents an 'added_to_merge_queue' event on a given pull request. + """ + ADDED_TO_MERGE_QUEUE_EVENT + + """ + Represents a 'removed_from_merge_queue' event on a given pull request. + """ + REMOVED_FROM_MERGE_QUEUE_EVENT + + """ + Represents a comment on an Issue. + """ + ISSUE_COMMENT + + """ + Represents a mention made by one issue or pull request to another. + """ + CROSS_REFERENCED_EVENT + + """ + Represents a 'added_to_project' event on a given issue or pull request. + """ + ADDED_TO_PROJECT_EVENT + + """ + Represents an 'assigned' event on any assignable object. + """ + ASSIGNED_EVENT + + """ + Represents a 'closed' event on any `Closable`. + """ + CLOSED_EVENT + + """ + Represents a 'comment_deleted' event on a given issue or pull request. + """ + COMMENT_DELETED_EVENT + + """ + Represents a 'connected' event on a given issue or pull request. + """ + CONNECTED_EVENT + + """ + Represents a 'converted_note_to_issue' event on a given issue or pull request. + """ + CONVERTED_NOTE_TO_ISSUE_EVENT + + """ + Represents a 'converted_to_discussion' event on a given issue. + """ + CONVERTED_TO_DISCUSSION_EVENT + + """ + Represents a 'demilestoned' event on a given issue or pull request. + """ + DEMILESTONED_EVENT + + """ + Represents a 'disconnected' event on a given issue or pull request. + """ + DISCONNECTED_EVENT + + """ + Represents a 'labeled' event on a given issue or pull request. + """ + LABELED_EVENT + + """ + Represents a 'locked' event on a given issue or pull request. + """ + LOCKED_EVENT + + """ + Represents a 'marked_as_duplicate' event on a given issue or pull request. + """ + MARKED_AS_DUPLICATE_EVENT + + """ + Represents a 'mentioned' event on a given issue or pull request. + """ + MENTIONED_EVENT + + """ + Represents a 'milestoned' event on a given issue or pull request. + """ + MILESTONED_EVENT + + """ + Represents a 'moved_columns_in_project' event on a given issue or pull request. + """ + MOVED_COLUMNS_IN_PROJECT_EVENT + + """ + Represents a 'pinned' event on a given issue or pull request. + """ + PINNED_EVENT + + """ + Represents a 'referenced' event on a given `ReferencedSubject`. + """ + REFERENCED_EVENT + + """ + Represents a 'removed_from_project' event on a given issue or pull request. + """ + REMOVED_FROM_PROJECT_EVENT + + """ + Represents a 'renamed' event on a given issue or pull request + """ + RENAMED_TITLE_EVENT + + """ + Represents a 'reopened' event on any `Closable`. + """ + REOPENED_EVENT + + """ + Represents a 'subscribed' event on a given `Subscribable`. + """ + SUBSCRIBED_EVENT + + """ + Represents a 'transferred' event on a given issue or pull request. + """ + TRANSFERRED_EVENT + + """ + Represents an 'unassigned' event on any assignable object. + """ + UNASSIGNED_EVENT + + """ + Represents an 'unlabeled' event on a given issue or pull request. + """ + UNLABELED_EVENT + + """ + Represents an 'unlocked' event on a given issue or pull request. + """ + UNLOCKED_EVENT + + """ + Represents a 'user_blocked' event on a given user. + """ + USER_BLOCKED_EVENT + + """ + Represents an 'unmarked_as_duplicate' event on a given issue or pull request. + """ + UNMARKED_AS_DUPLICATE_EVENT + + """ + Represents an 'unpinned' event on a given issue or pull request. + """ + UNPINNED_EVENT + + """ + Represents an 'unsubscribed' event on a given `Subscribable`. + """ + UNSUBSCRIBED_EVENT +} + +""" +The possible target states when updating a pull request. +""" +enum PullRequestUpdateState { + """ + A pull request that is still open. + """ + OPEN + + """ + A pull request that has been closed without being merged. + """ + CLOSED +} + +""" +A Git push. +""" +type Push implements Node { + """ + The Node ID of the Push object + """ + id: ID! + + """ + The SHA after the push + """ + nextSha: GitObjectID + + """ + The permalink for this push. + """ + permalink: URI! + + """ + The SHA before the push + """ + previousSha: GitObjectID + + """ + The actor who pushed + """ + pusher: Actor! + + """ + The repository that was pushed to + """ + repository: Repository! +} + +""" +A team, user, or app who has the ability to push to a protected branch. +""" +type PushAllowance implements Node { + """ + The actor that can push. + """ + actor: PushAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + + """ + The Node ID of the PushAllowance object + """ + id: ID! +} + +""" +Types that can be an actor. +""" +union PushAllowanceActor = App|Team|User + +""" +The connection type for PushAllowance. +""" +type PushAllowanceConnection { + """ + A list of edges. + """ + edges: [PushAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [PushAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type PushAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: PushAllowance +} + +""" +The query root of GitHub's GraphQL interface. +""" +type Query { + """ + Look up a code of conduct by its key + """ + codeOfConduct("The code of conduct's key" key: String!): CodeOfConduct + + """ + Look up a code of conduct by its key + """ + codesOfConduct: [CodeOfConduct] + + """ + Look up an enterprise by URL slug. + """ + enterprise("The enterprise URL slug." slug: String!, "The enterprise invitation token." invitationToken: String): Enterprise + + """ + Look up a pending enterprise administrator invitation by invitee, enterprise and role. + """ + enterpriseAdministratorInvitation("The login of the user invited to join the business." userLogin: String!, "The slug of the enterprise the user was invited to join." enterpriseSlug: String!, "The role for the business member invitation." role: EnterpriseAdministratorRole!): EnterpriseAdministratorInvitation + + """ + Look up a pending enterprise administrator invitation by invitation token. + """ + enterpriseAdministratorInvitationByToken("The invitation token sent with the invitation email." invitationToken: String!): EnterpriseAdministratorInvitation + + """ + Look up a pending enterprise unaffiliated member invitation by invitee and enterprise. + """ + enterpriseMemberInvitation("The login of the user invited to join the business." userLogin: String!, "The slug of the enterprise the user was invited to join." enterpriseSlug: String!): EnterpriseMemberInvitation + + """ + Look up a pending enterprise unaffiliated member invitation by invitation token. + """ + enterpriseMemberInvitationByToken("The invitation token sent with the invitation email." invitationToken: String!): EnterpriseMemberInvitation + + """ + Look up an open source license by its key + """ + license("The license's downcased SPDX ID" key: String!): License + + """ + Return a list of known open source licenses + """ + licenses: [License]! + + """ + Get alphabetically sorted list of Marketplace categories + """ + marketplaceCategories("Return only the specified categories." includeCategories: [String!], "Exclude categories with no listings." excludeEmpty: Boolean, "Returns top level categories only, excluding any subcategories." excludeSubcategories: Boolean): [MarketplaceCategory!]! + + """ + Look up a Marketplace category by its slug. + """ + marketplaceCategory("The URL slug of the category." slug: String!, "Also check topic aliases for the category slug" useTopicAliases: Boolean): MarketplaceCategory + + """ + Look up a single Marketplace listing + """ + marketplaceListing("Select the listing that matches this slug. It's the short name of the listing used in its URL." slug: String!): MarketplaceListing + + """ + Look up Marketplace listings + """ + marketplaceListings("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Select only listings with the given category." categorySlug: String, "Also check topic aliases for the category slug" useTopicAliases: Boolean, "Select listings to which user has admin access. If omitted, listings visible to the\nviewer are returned.\n" viewerCanAdmin: Boolean, "Select listings that can be administered by the specified user." adminId: ID, "Select listings for products owned by the specified organization." organizationId: ID, "Select listings visible to the viewer even if they are not approved. If omitted or\nfalse, only approved listings will be returned.\n" allStates: Boolean, "Select the listings with these slugs, if they are visible to the viewer." slugs: [String], "Select only listings where the primary category matches the given category slug." primaryCategoryOnly: Boolean = false, "Select only listings that offer a free trial." withFreeTrialsOnly: Boolean = false): MarketplaceListingConnection! + + """ + Return information about the GitHub instance + """ + meta: GitHubMetadata! + + """ + Fetches an object given its ID. + """ + node("ID of the object." id: ID!): Node + + """ + Lookup nodes by a list of IDs. + """ + nodes("The list of node IDs." ids: [ID!]!): [Node]! + + """ + Lookup a organization by login. + """ + organization("The organization's login." login: String!): Organization + + """ + The client's rate limit information. + """ + rateLimit("If true, calculate the cost for the query without evaluating it" dryRun: Boolean = false): RateLimit + + """ + Workaround for re-exposing the root query object. (Refer to https://github.com/facebook/relay/issues/112 for more information.) + """ + relay: Query! + + """ + Lookup a given repository by the owner and repository name. + """ + repository("The login field of a user or organization" owner: String!, "The name of the repository" name: String!, "Follow repository renames. If disabled, a repository referenced by its old name will return an error." followRenames: Boolean = true): Repository + + """ + Lookup a repository owner (ie. either a User or an Organization) by login. + """ + repositoryOwner("The username to lookup the owner by." login: String!): RepositoryOwner + + """ + Lookup resource by a URL. + """ + resource("The URL." url: URI!): UniformResourceLocatable + + """ + Perform a search across resources, returning a maximum of 1,000 results. + """ + search("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The search string to look for. GitHub search syntax is supported. For more information, see \"[Searching on GitHub](https:\/\/docs.github.com\/search-github\/searching-on-github),\" \"[Understanding the search syntax](https:\/\/docs.github.com\/search-github\/getting-started-with-searching-on-github\/understanding-the-search-syntax),\" and \"[Sorting search results](https:\/\/docs.github.com\/search-github\/getting-started-with-searching-on-github\/sorting-search-results).\"" query: String!, "The types of search items to search within." type: SearchType!): SearchResultItemConnection! + + """ + GitHub Security Advisories + """ + securityAdvisories("Ordering options for the returned topics." orderBy: SecurityAdvisoryOrder = { + field: UPDATED_AT + direction: DESC + } + , "Filter advisories by identifier, e.g. GHSA or CVE." identifier: SecurityAdvisoryIdentifierFilter, "Filter advisories to those published since a time in the past." publishedSince: DateTime, "Filter advisories to those updated since a time in the past." updatedSince: DateTime, "A list of classifications to filter advisories by." classifications: [SecurityAdvisoryClassification!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SecurityAdvisoryConnection! + + """ + Fetch a Security Advisory by its GHSA ID + """ + securityAdvisory("GitHub Security Advisory ID." ghsaId: String!): SecurityAdvisory + + """ + Software Vulnerabilities documented by GitHub Security Advisories + """ + securityVulnerabilities("Ordering options for the returned topics." orderBy: SecurityVulnerabilityOrder = { + field: UPDATED_AT + direction: DESC + } + , "An ecosystem to filter vulnerabilities by." ecosystem: SecurityAdvisoryEcosystem, "A package name to filter vulnerabilities by." package: String, "A list of severities to filter vulnerabilities by." severities: [SecurityAdvisorySeverity!], "A list of advisory classifications to filter vulnerabilities by." classifications: [SecurityAdvisoryClassification!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SecurityVulnerabilityConnection! + + """ + Users and organizations who can be sponsored via GitHub Sponsors. + """ + sponsorables("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for users and organizations returned from the connection." orderBy: SponsorableOrder = { + field: LOGIN + direction: ASC + } + , "Whether only sponsorables who own the viewer's dependencies will be returned. Must be authenticated to use. Can check an organization instead for their dependencies owned by sponsorables by passing orgLoginForDependencies." onlyDependencies: Boolean = false, "Optional organization username for whose dependencies should be checked. Used when onlyDependencies = true. Omit to check your own dependencies. If you are not an administrator of the organization, only dependencies from its public repositories will be considered." orgLoginForDependencies: String, "Optional filter for which dependencies should be checked for sponsorable owners. Only sponsorable owners of dependencies in this ecosystem will be included. Used when onlyDependencies = true.\n\n**Upcoming Change on 2022-07-01 UTC**\n**Description:** `dependencyEcosystem` will be removed. Use the ecosystem argument instead.\n**Reason:** The type is switching from SecurityAdvisoryEcosystem to DependencyGraphEcosystem.\n" dependencyEcosystem: SecurityAdvisoryEcosystem, "Optional filter for which dependencies should be checked for sponsorable owners. Only sponsorable owners of dependencies in this ecosystem will be included. Used when onlyDependencies = true." ecosystem: DependencyGraphEcosystem): SponsorableItemConnection! + + """ + Look up a topic by name. + """ + topic("The topic's name." name: String!): Topic + + """ + Lookup a user by login. + """ + user("The user's login." login: String!): User + + """ + The currently authenticated user. + """ + viewer: User! +} + +""" +Represents the client's rate limit. +""" +type RateLimit { + """ + The point cost for the current query counting against the rate limit. + """ + cost: Int! + + """ + The maximum number of points the client is permitted to consume in a 60 minute window. + """ + limit: Int! + + """ + The maximum number of nodes this query may return + """ + nodeCount: Int! + + """ + The number of points remaining in the current rate limit window. + """ + remaining: Int! + + """ + The time at which the current rate limit window resets in UTC epoch seconds. + """ + resetAt: DateTime! + + """ + The number of points used in the current rate limit window. + """ + used: Int! +} + +""" +Represents a subject that can be reacted on. +""" +interface Reactable { + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Reactable object + """ + id: ID! + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! +} + +""" +The connection type for User. +""" +type ReactingUserConnection { + """ + A list of edges. + """ + edges: [ReactingUserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's made a reaction. +""" +type ReactingUserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + node: User! + + """ + The moment when the user made the reaction. + """ + reactedAt: DateTime! +} + +""" +An emoji reaction to a particular piece of content. +""" +type Reaction implements Node { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Reaction object + """ + id: ID! + + """ + The reactable piece of content + """ + reactable: Reactable! + + """ + Identifies the user who created this reaction. + """ + user: User +} + +""" +A list of reactions that have been left on the subject. +""" +type ReactionConnection { + """ + A list of edges. + """ + edges: [ReactionEdge] + + """ + A list of nodes. + """ + nodes: [Reaction] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Emojis that can be attached to Issues, Pull Requests and Comments. +""" +enum ReactionContent { + """ + Represents the `:+1:` emoji. + """ + THUMBS_UP + + """ + Represents the `:-1:` emoji. + """ + THUMBS_DOWN + + """ + Represents the `:laugh:` emoji. + """ + LAUGH + + """ + Represents the `:hooray:` emoji. + """ + HOORAY + + """ + Represents the `:confused:` emoji. + """ + CONFUSED + + """ + Represents the `:heart:` emoji. + """ + HEART + + """ + Represents the `:rocket:` emoji. + """ + ROCKET + + """ + Represents the `:eyes:` emoji. + """ + EYES +} + +""" +An edge in a connection. +""" +type ReactionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Reaction +} + +""" +A group of emoji reactions to a particular piece of content. +""" +type ReactionGroup { + """ + Identifies the emoji reaction. + """ + content: ReactionContent! + + """ + Identifies when the reaction was created. + """ + createdAt: DateTime + + """ + Reactors to the reaction subject with the emotion represented by this reaction group. + """ + reactors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ReactorConnection! + + """ + The subject that was reacted to. + """ + subject: Reactable! + + """ + Users who have reacted to the reaction subject with the emotion represented by this reaction group + """ + users("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ReactingUserConnection! @deprecated(reason: "Reactors can now be mannequins, bots, and organizations. Use the `reactors` field instead. Removal on 2021-10-01 UTC.") + + """ + Whether or not the authenticated user has left a reaction on the subject. + """ + viewerHasReacted: Boolean! +} + +""" +Ways in which lists of reactions can be ordered upon return. +""" +input ReactionOrder { + """ + The field in which to order reactions by. + """ + field: ReactionOrderField! + + """ + The direction in which to order reactions by the specified field. + """ + direction: OrderDirection! +} + +""" +A list of fields that reactions can be ordered by. +""" +enum ReactionOrderField { + """ + Allows ordering a list of reactions by when they were created. + """ + CREATED_AT +} + +""" +Types that can be assigned to reactions. +""" +union Reactor = Bot|Mannequin|Organization|User + +""" +The connection type for Reactor. +""" +type ReactorConnection { + """ + A list of edges. + """ + edges: [ReactorEdge] + + """ + A list of nodes. + """ + nodes: [Reactor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents an author of a reaction. +""" +type ReactorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The author of the reaction. + """ + node: Reactor! + + """ + The moment when the user made the reaction. + """ + reactedAt: DateTime! +} + +""" +Represents a 'ready_for_review' event on a given pull request. +""" +type ReadyForReviewEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ReadyForReviewEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + The HTTP path for this ready for review event. + """ + resourcePath: URI! + + """ + The HTTP URL for this ready for review event. + """ + url: URI! +} + +""" +Represents a Git reference. +""" +type Ref implements Node { + """ + A list of pull requests with this ref as the head ref. + """ + associatedPullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection! + + """ + Branch protection rules for this ref + """ + branchProtectionRule: BranchProtectionRule + + """ + Compares the current ref as a base ref to another head ref, if the comparison can be made. + """ + compare("The head ref to compare against." headRef: String!): Comparison + + """ + The Node ID of the Ref object + """ + id: ID! + + """ + The ref name. + """ + name: String! + + """ + The ref's prefix, such as `refs/heads/` or `refs/tags/`. + """ + prefix: String! + + """ + Branch protection rules that are viewable by non-admins + """ + refUpdateRule: RefUpdateRule + + """ + The repository the ref belongs to. + """ + repository: Repository! + + """ + A list of rules from active Repository and Organization rulesets that apply to this ref. + """ + rules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for repository rules." orderBy: RepositoryRuleOrder = { + field: UPDATED_AT + direction: DESC + } + ): RepositoryRuleConnection + + """ + The object the ref points to. Returns null when object does not exist. + """ + target: GitObject +} + +""" +The connection type for Ref. +""" +type RefConnection { + """ + A list of edges. + """ + edges: [RefEdge] + + """ + A list of nodes. + """ + nodes: [Ref] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RefEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Ref +} + +""" +Parameters to be used for the ref_name condition +""" +type RefNameConditionTarget { + """ + Array of ref names or patterns to exclude. The condition will not pass if any of these patterns match. + """ + exclude: [String!]! + + """ + Array of ref names or patterns to include. One of these patterns must match for the condition to pass. Also accepts `~DEFAULT_BRANCH` to include the default branch or `~ALL` to include all branches. + """ + include: [String!]! +} + +""" +Parameters to be used for the ref_name condition +""" +input RefNameConditionTargetInput { + """ + Array of ref names or patterns to exclude. The condition will not pass if any of these patterns match. + """ + exclude: [String!]! + + """ + Array of ref names or patterns to include. One of these patterns must match for the condition to pass. Also accepts `~DEFAULT_BRANCH` to include the default branch or `~ALL` to include all branches. + """ + include: [String!]! +} + +""" +Ways in which lists of git refs can be ordered upon return. +""" +input RefOrder { + """ + The field in which to order refs by. + """ + field: RefOrderField! + + """ + The direction in which to order refs by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which ref connections can be ordered. +""" +enum RefOrderField { + """ + Order refs by underlying commit date if the ref prefix is refs/tags/ + """ + TAG_COMMIT_DATE + + """ + Order refs by their alphanumeric name + """ + ALPHABETICAL +} + +""" +A ref update +""" +input RefUpdate { + """ + The fully qualified name of the ref to be update. For example `refs/heads/branch-name` + """ + name: GitRefname! + + """ + The value this ref should be updated to. + """ + afterOid: GitObjectID! + + """ + The value this ref needs to point to before the update. + """ + beforeOid: GitObjectID + + """ + Force a non fast-forward update. + """ + force: Boolean = false +} + +""" +Branch protection rules that are enforced on the viewer. +""" +type RefUpdateRule { + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean! + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean! + + """ + Can matching branches be created. + """ + blocksCreations: Boolean! + + """ + Identifies the protection rule pattern. + """ + pattern: String! + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String] + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean! + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean! + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean! + + """ + Are commits required to be signed. + """ + requiresSignatures: Boolean! + + """ + Is the viewer allowed to dismiss reviews. + """ + viewerAllowedToDismissReviews: Boolean! + + """ + Can the viewer push to the branch + """ + viewerCanPush: Boolean! +} + +""" +Represents a 'referenced' event on a given `ReferencedSubject`. +""" +type ReferencedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the commit associated with the 'referenced' event. + """ + commit: Commit + + """ + Identifies the repository associated with the 'referenced' event. + """ + commitRepository: Repository! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ReferencedEvent object + """ + id: ID! + + """ + Reference originated in a different repository. + """ + isCrossRepository: Boolean! + + """ + Checks if the commit message itself references the subject. Can be false in the case of a commit comment reference. + """ + isDirectReference: Boolean! + + """ + Object referenced by event. + """ + subject: ReferencedSubject! +} + +""" +Any referencable object +""" +union ReferencedSubject = Issue|PullRequest + +""" +Autogenerated input type of RegenerateEnterpriseIdentityProviderRecoveryCodes +""" +input RegenerateEnterpriseIdentityProviderRecoveryCodesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! +} + +""" +Autogenerated return type of RegenerateEnterpriseIdentityProviderRecoveryCodes. +""" +type RegenerateEnterpriseIdentityProviderRecoveryCodesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RegenerateVerifiableDomainToken +""" +input RegenerateVerifiableDomainTokenInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to regenerate the verification token of. + """ + id: ID! +} + +""" +Autogenerated return type of RegenerateVerifiableDomainToken. +""" +type RegenerateVerifiableDomainTokenPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verification token that was generated. + """ + verificationToken: String +} + +""" +Autogenerated input type of RejectDeployments +""" +input RejectDeploymentsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the workflow run containing the pending deployments. + """ + workflowRunId: ID! + + """ + The ids of environments to reject deployments + """ + environmentIds: [ID!]! + + """ + Optional comment for rejecting deployments + """ + comment: String = "" +} + +""" +Autogenerated return type of RejectDeployments. +""" +type RejectDeploymentsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The affected deployments. + """ + deployments: [Deployment!] +} + +""" +A release contains the content for a release. +""" +type Release implements Node & Reactable & UniformResourceLocatable { + """ + The author of the release + """ + author: User + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the release. + """ + description: String + + """ + The description of this release rendered to HTML. + """ + descriptionHTML: HTML + + """ + The Node ID of the Release object + """ + id: ID! + + """ + Whether or not the release is a draft + """ + isDraft: Boolean! + + """ + Whether or not the release is the latest releast + """ + isLatest: Boolean! + + """ + Whether or not the release is a prerelease + """ + isPrerelease: Boolean! + + """ + A list of users mentioned in the release description + """ + mentions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection + + """ + The title of the release. + """ + name: String + + """ + Identifies the date and time when the release was created. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + List of releases assets which are dependent on this release. + """ + releaseAssets("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A name to filter the assets by." name: String): ReleaseAssetConnection! + + """ + The repository that the release belongs to. + """ + repository: Repository! + + """ + The HTTP path for this issue + """ + resourcePath: URI! + + """ + A description of the release, rendered to HTML without any links in it. + """ + shortDescriptionHTML("How many characters to return." limit: Int = 200): HTML + + """ + The Git tag the release points to + """ + tag: Ref + + """ + The tag commit for this release. + """ + tagCommit: Commit + + """ + The name of the release's Git tag + """ + tagName: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this issue + """ + url: URI! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! +} + +""" +A release asset contains the content for a release asset. +""" +type ReleaseAsset implements Node { + """ + The asset's content-type + """ + contentType: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The number of times this asset was downloaded + """ + downloadCount: Int! + + """ + Identifies the URL where you can download the release asset via the browser. + """ + downloadUrl: URI! + + """ + The Node ID of the ReleaseAsset object + """ + id: ID! + + """ + Identifies the title of the release asset. + """ + name: String! + + """ + Release that the asset is associated with + """ + release: Release + + """ + The size (in bytes) of the asset + """ + size: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user that performed the upload + """ + uploadedBy: User! + + """ + Identifies the URL of the release asset. + """ + url: URI! +} + +""" +The connection type for ReleaseAsset. +""" +type ReleaseAssetConnection { + """ + A list of edges. + """ + edges: [ReleaseAssetEdge] + + """ + A list of nodes. + """ + nodes: [ReleaseAsset] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseAssetEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReleaseAsset +} + +""" +The connection type for Release. +""" +type ReleaseConnection { + """ + A list of edges. + """ + edges: [ReleaseEdge] + + """ + A list of nodes. + """ + nodes: [Release] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReleaseEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Release +} + +""" +Ways in which lists of releases can be ordered upon return. +""" +input ReleaseOrder { + """ + The field in which to order releases by. + """ + field: ReleaseOrderField! + + """ + The direction in which to order releases by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which release connections can be ordered. +""" +enum ReleaseOrderField { + """ + Order releases by creation time + """ + CREATED_AT + + """ + Order releases alphabetically by name + """ + NAME +} + +""" +Autogenerated input type of RemoveAssigneesFromAssignable +""" +input RemoveAssigneesFromAssignableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the assignable object to remove assignees from. + """ + assignableId: ID! + + """ + The id of users to remove as assignees. + """ + assigneeIds: [ID!]! +} + +""" +Autogenerated return type of RemoveAssigneesFromAssignable. +""" +type RemoveAssigneesFromAssignablePayload { + """ + The item that was unassigned. + """ + assignable: Assignable + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of RemoveEnterpriseAdmin +""" +input RemoveEnterpriseAdminInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Enterprise ID from which to remove the administrator. + """ + enterpriseId: ID! + + """ + The login of the user to remove as an administrator. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseAdmin. +""" +type RemoveEnterpriseAdminPayload { + """ + The user who was removed as an administrator. + """ + admin: User + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + A message confirming the result of removing an administrator. + """ + message: String + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseIdentityProvider +""" +input RemoveEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which to remove the identity provider. + """ + enterpriseId: ID! +} + +""" +Autogenerated return type of RemoveEnterpriseIdentityProvider. +""" +type RemoveEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider that was removed from the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of RemoveEnterpriseMember +""" +input RemoveEnterpriseMemberInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which the user should be removed. + """ + enterpriseId: ID! + + """ + The ID of the user to remove from the enterprise. + """ + userId: ID! +} + +""" +Autogenerated return type of RemoveEnterpriseMember. +""" +type RemoveEnterpriseMemberPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + The user that was removed from the enterprise. + """ + user: User + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseOrganization +""" +input RemoveEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise from which the organization should be removed. + """ + enterpriseId: ID! + + """ + The ID of the organization to remove from the enterprise. + """ + organizationId: ID! +} + +""" +Autogenerated return type of RemoveEnterpriseOrganization. +""" +type RemoveEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise + + """ + The organization that was removed from the enterprise. + """ + organization: Organization + + """ + The viewer performing the mutation. + """ + viewer: User +} + +""" +Autogenerated input type of RemoveEnterpriseSupportEntitlement +""" +input RemoveEnterpriseSupportEntitlementInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! + + """ + The login of a member who will lose the support entitlement. + """ + login: String! +} + +""" +Autogenerated return type of RemoveEnterpriseSupportEntitlement. +""" +type RemoveEnterpriseSupportEntitlementPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of removing the support entitlement. + """ + message: String +} + +""" +Autogenerated input type of RemoveLabelsFromLabelable +""" +input RemoveLabelsFromLabelableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the Labelable to remove labels from. + """ + labelableId: ID! + + """ + The ids of labels to remove. + """ + labelIds: [ID!]! +} + +""" +Autogenerated return type of RemoveLabelsFromLabelable. +""" +type RemoveLabelsFromLabelablePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Labelable the labels were removed from. + """ + labelable: Labelable +} + +""" +Autogenerated input type of RemoveOutsideCollaborator +""" +input RemoveOutsideCollaboratorInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the outside collaborator to remove. + """ + userId: ID! + + """ + The ID of the organization to remove the outside collaborator from. + """ + organizationId: ID! +} + +""" +Autogenerated return type of RemoveOutsideCollaborator. +""" +type RemoveOutsideCollaboratorPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was removed as an outside collaborator. + """ + removedUser: User +} + +""" +Autogenerated input type of RemoveReaction +""" +input RemoveReactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! + + """ + The name of the emoji reaction to remove. + """ + content: ReactionContent! +} + +""" +Autogenerated return type of RemoveReaction. +""" +type RemoveReactionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The reaction object. + """ + reaction: Reaction + + """ + The reaction groups for the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + The reactable subject. + """ + subject: Reactable +} + +""" +Autogenerated input type of RemoveStar +""" +input RemoveStarInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Starrable ID to unstar. + """ + starrableId: ID! +} + +""" +Autogenerated return type of RemoveStar. +""" +type RemoveStarPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The starrable. + """ + starrable: Starrable +} + +""" +Autogenerated input type of RemoveUpvote +""" +input RemoveUpvoteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion or comment to remove upvote. + """ + subjectId: ID! +} + +""" +Autogenerated return type of RemoveUpvote. +""" +type RemoveUpvotePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The votable subject. + """ + subject: Votable +} + +""" +Represents a 'removed_from_merge_queue' event on a given pull request. +""" +type RemovedFromMergeQueueEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the before commit SHA for the 'removed_from_merge_queue' event. + """ + beforeCommit: Commit + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The user who removed this Pull Request from the merge queue + """ + enqueuer: User + + """ + The Node ID of the RemovedFromMergeQueueEvent object + """ + id: ID! + + """ + The merge queue where this pull request was removed from. + """ + mergeQueue: MergeQueue + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest + + """ + The reason this pull request was removed from the queue. + """ + reason: String +} + +""" +Represents a 'removed_from_project' event on a given issue or pull request. +""" +type RemovedFromProjectEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the RemovedFromProjectEvent object + """ + id: ID! + + """ + Project referenced by event. + """ + project: Project + + """ + Column name referenced by this project event. + """ + projectColumnName: String! +} + +""" +Represents a 'renamed' event on a given issue or pull request +""" +type RenamedTitleEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the current title of the issue or pull request. + """ + currentTitle: String! + + """ + The Node ID of the RenamedTitleEvent object + """ + id: ID! + + """ + Identifies the previous title of the issue or pull request. + """ + previousTitle: String! + + """ + Subject that was renamed. + """ + subject: RenamedTitleSubject! +} + +""" +An object which has a renamable title +""" +union RenamedTitleSubject = Issue|PullRequest + +""" +Autogenerated input type of ReopenDiscussion +""" +input ReopenDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the discussion to be reopened. + """ + discussionId: ID! +} + +""" +Autogenerated return type of ReopenDiscussion. +""" +type ReopenDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that was reopened. + """ + discussion: Discussion +} + +""" +Autogenerated input type of ReopenIssue +""" +input ReopenIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue to be opened. + """ + issueId: ID! +} + +""" +Autogenerated return type of ReopenIssue. +""" +type ReopenIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was opened. + """ + issue: Issue +} + +""" +Autogenerated input type of ReopenPullRequest +""" +input ReopenPullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the pull request to be reopened. + """ + pullRequestId: ID! +} + +""" +Autogenerated return type of ReopenPullRequest. +""" +type ReopenPullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was reopened. + """ + pullRequest: PullRequest +} + +""" +Represents a 'reopened' event on any `Closable`. +""" +type ReopenedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Object that was reopened. + """ + closable: Closable! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ReopenedEvent object + """ + id: ID! + + """ + The reason the issue state was changed to open. + """ + stateReason: IssueStateReason +} + +""" +Autogenerated input type of ReorderEnvironment +""" +input ReorderEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the environment to modify + """ + environmentId: ID! + + """ + The desired position of the environment + """ + position: Int! +} + +""" +Autogenerated return type of ReorderEnvironment. +""" +type ReorderEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The environment that was reordered + """ + environment: Environment +} + +""" +Audit log entry for a repo.access event. +""" +type RepoAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoAccessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAccessAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAccessAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_member event. +""" +type RepoAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoAddMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoAddMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoAddMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.add_topic event. +""" +type RepoAddTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoAddTopicAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.archived event. +""" +type RepoArchivedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoArchivedAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoArchivedAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoArchivedAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.change_merge_setting event. +""" +type RepoChangeMergeSettingAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoChangeMergeSettingAuditEntry object + """ + id: ID! + + """ + Whether the change was to enable (true) or disable (false) the merge type + """ + isEnabled: Boolean + + """ + The merge method affected by the change + """ + mergeType: RepoChangeMergeSettingAuditEntryMergeType + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The merge options available for pull requests to this repository. +""" +enum RepoChangeMergeSettingAuditEntryMergeType { + """ + The pull request is added to the base branch in a merge commit. + """ + MERGE + + """ + Commits from the pull request are added onto the base branch individually without a merge commit. + """ + REBASE + + """ + The pull request's commits are squashed into a single commit before they are merged to the base branch. + """ + SQUASH +} + +""" +Audit log entry for a repo.config.disable_anonymous_git_access event. +""" +type RepoConfigDisableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigDisableAnonymousGitAccessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_collaborators_only event. +""" +type RepoConfigDisableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigDisableCollaboratorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_contributors_only event. +""" +type RepoConfigDisableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigDisableContributorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.disable_sockpuppet_disallowed event. +""" +type RepoConfigDisableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigDisableSockpuppetDisallowedAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_anonymous_git_access event. +""" +type RepoConfigEnableAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigEnableAnonymousGitAccessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_collaborators_only event. +""" +type RepoConfigEnableCollaboratorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigEnableCollaboratorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_contributors_only event. +""" +type RepoConfigEnableContributorsOnlyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigEnableContributorsOnlyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.enable_sockpuppet_disallowed event. +""" +type RepoConfigEnableSockpuppetDisallowedAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigEnableSockpuppetDisallowedAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.lock_anonymous_git_access event. +""" +type RepoConfigLockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigLockAnonymousGitAccessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.config.unlock_anonymous_git_access event. +""" +type RepoConfigUnlockAnonymousGitAccessAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoConfigUnlockAnonymousGitAccessAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repo.create event. +""" +type RepoCreateAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The name of the parent repository for this forked repository. + """ + forkParentName: String + + """ + The name of the root repository for this network. + """ + forkSourceName: String + + """ + The Node ID of the RepoCreateAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoCreateAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoCreateAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.destroy event. +""" +type RepoDestroyAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoDestroyAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoDestroyAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoDestroyAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_member event. +""" +type RepoRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoRemoveMemberAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI + + """ + The visibility of the repository + """ + visibility: RepoRemoveMemberAuditEntryVisibility +} + +""" +The privacy of a repository +""" +enum RepoRemoveMemberAuditEntryVisibility { + """ + The repository is visible only to users in the same business. + """ + INTERNAL + + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC +} + +""" +Audit log entry for a repo.remove_topic event. +""" +type RepoRemoveTopicAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TopicAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the RepoRemoveTopicAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The reasons a piece of content can be reported or minimized. +""" +enum ReportedContentClassifiers { + """ + A spammy piece of content + """ + SPAM + + """ + An abusive or harassing piece of content + """ + ABUSE + + """ + An irrelevant piece of content + """ + OFF_TOPIC + + """ + An outdated piece of content + """ + OUTDATED + + """ + A duplicated piece of content + """ + DUPLICATE + + """ + The content has been resolved + """ + RESOLVED +} + +""" +A repository contains the content for a project. +""" +type Repository implements Node & PackageOwner & ProjectOwner & ProjectV2Recent & RepositoryInfo & Starrable & Subscribable & UniformResourceLocatable { + """ + Whether or not a pull request head branch that is behind its base branch can always be updated even if it is not required to be up to date before merging. + """ + allowUpdateBranch: Boolean! + + """ + Identifies the date and time when the repository was archived. + """ + archivedAt: DateTime + + """ + A list of users that can be assigned to issues in this repository. + """ + assignableUsers("Filters users with query on user name and login." query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + Whether or not Auto-merge can be enabled on pull requests in this repository. + """ + autoMergeAllowed: Boolean! + + """ + A list of branch protection rules for this repository. + """ + branchProtectionRules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): BranchProtectionRuleConnection! + + """ + Returns the code of conduct for this repository + """ + codeOfConduct: CodeOfConduct + + """ + Information extracted from the repository's `CODEOWNERS` file. + """ + codeowners("The ref name used to return the associated `CODEOWNERS` file." refName: String): RepositoryCodeowners + + """ + A list of collaborators associated with the repository. + """ + collaborators("Collaborators affiliation level with a repository." affiliation: CollaboratorAffiliation, "The login of one specific collaborator." login: String, "Filters users with query on user name and login" query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryCollaboratorConnection + + """ + A list of commit comments associated with the repository. + """ + commitComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitCommentConnection! + + """ + Returns a list of contact links associated to the repository + """ + contactLinks: [RepositoryContactLink!] + + """ + Returns the contributing guidelines for this repository. + """ + contributingGuidelines: ContributingGuidelines + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Ref associated with the repository's default branch. + """ + defaultBranchRef: Ref + + """ + Whether or not branches are automatically deleted when merged in this repository. + """ + deleteBranchOnMerge: Boolean! + + """ + A list of dependency manifests contained in the repository + """ + dependencyGraphManifests("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Flag to scope to only manifests with dependencies" withDependencies: Boolean, "Number of dependencies to fetch" dependenciesFirst: Int, "Cursor to paginate dependencies" dependenciesAfter: String): DependencyGraphManifestConnection + + """ + A list of deploy keys that are on this repository. + """ + deployKeys("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeployKeyConnection! + + """ + Deployments associated with the repository + """ + deployments("Environments to list deployments for" environments: [String!], "Ordering options for deployments returned from the connection." orderBy: DeploymentOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentConnection! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + Returns a single discussion from the current repository by number. + """ + discussion("The number for the discussion to be returned." number: Int!): Discussion + + """ + A list of discussion categories that are available in the repository. + """ + discussionCategories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter by categories that are assignable by the viewer." filterByAssignable: Boolean = false): DiscussionCategoryConnection! + + """ + A discussion category by slug. + """ + discussionCategory("The slug of the discussion category to be returned." slug: String!): DiscussionCategory + + """ + A list of discussions that have been opened in the repository. + """ + discussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Only include discussions that belong to the category with this ID." categoryId: ID = null, "A list of states to filter the discussions by." states: [DiscussionState!] = [], "Ordering options for discussions returned from the connection." orderBy: DiscussionOrder = { + field: UPDATED_AT + direction: DESC + } + , "Only show answered or unanswered discussions" answered: Boolean = null): DiscussionConnection! + + """ + The number of kilobytes this repository occupies on disk. + """ + diskUsage: Int + + """ + Returns a single active environment from the current repository by name. + """ + environment("The name of the environment to be returned." name: String!): Environment + + """ + A list of environments that are in this repository. + """ + environments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the environments" orderBy: Environments = { + field: NAME + direction: ASC + } + , "Filter to control pinned environments return" pinnedEnvironmentFilter: EnvironmentPinnedFilterField = ALL, "The names of the environments to be returned." names: [String!] = []): EnvironmentConnection! + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + Whether this repository allows forks. + """ + forkingAllowed: Boolean! + + """ + A list of direct forked repositories. + """ + forks("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Array of viewer's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the current viewer owns." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryConnection! + + """ + The funding links for this repository + """ + fundingLinks: [FundingLink!]! + + """ + Indicates if the repository has the Discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository displays a Sponsor button for financial contributions. + """ + hasSponsorshipsEnabled: Boolean! + + """ + Whether vulnerability alerts are enabled for the repository. + """ + hasVulnerabilityAlertsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + + """ + The Node ID of the Repository object + """ + id: ID! + + """ + The interaction ability settings for this repository. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Returns true if blank issue creation is allowed + """ + isBlankIssuesEnabled: Boolean! + + """ + Returns whether or not this repository disabled. + """ + isDisabled: Boolean! + + """ + Returns whether or not this repository is empty. + """ + isEmpty: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + Returns true if this repository has a security policy + """ + isSecurityPolicyEnabled: Boolean + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + Is this repository a user configuration repository? + """ + isUserConfigurationRepository: Boolean! + + """ + Returns a single issue from the current repository by number. + """ + issue("The number for the issue to be returned." number: Int!): Issue + + """ + Returns a single issue-like object from the current repository by number. + """ + issueOrPullRequest("The number for the issue to be returned." number: Int!): IssueOrPullRequest + + """ + Returns a list of issue templates associated to the repository + """ + issueTemplates: [IssueTemplate!] + + """ + A list of issues that have been opened in the repository. + """ + issues("Ordering options for issues returned from the connection." orderBy: IssueOrder, "A list of label names to filter the pull requests by." labels: [String!], "A list of states to filter the issues by." states: [IssueState!], "Filtering options for issues returned from the connection." filterBy: IssueFilters, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + Returns a single label by name + """ + label("Label name" name: String!): Label + + """ + A list of labels associated with the repository. + """ + labels("Ordering options for labels returned from the connection." orderBy: LabelOrder = { + field: CREATED_AT + direction: ASC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If provided, searches labels by name and description." query: String): LabelConnection + + """ + A list containing a breakdown of the language composition of the repository. + """ + languages("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: LanguageOrder): LanguageConnection + + """ + Get the latest release for the repository if one exists. + """ + latestRelease: Release + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + A list of Users that can be mentioned in the context of the repository. + """ + mentionableUsers("Filters users with query on user name and login" query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + Whether or not PRs are merged with a merge commit on this repository. + """ + mergeCommitAllowed: Boolean! + + """ + How the default commit message will be generated when merging a pull request. + """ + mergeCommitMessage: MergeCommitMessage! + + """ + How the default commit title will be generated when merging a pull request. + """ + mergeCommitTitle: MergeCommitTitle! + + """ + The merge queue for a specified branch, otherwise the default branch if not provided. + """ + mergeQueue("The name of the branch to get the merge queue for. Case sensitive." branch: String): MergeQueue + + """ + Returns a single milestone from the current repository by number. + """ + milestone("The number for the milestone to be returned." number: Int!): Milestone + + """ + A list of milestones associated with the repository. + """ + milestones("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter by the state of the milestones." states: [MilestoneState!], "Ordering options for milestones." orderBy: MilestoneOrder, "Filters milestones with a query on the title" query: String): MilestoneConnection + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + A Git object in the repository + """ + object("The Git object ID" oid: GitObjectID, "A Git revision expression suitable for rev-parse" expression: String): GitObject + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + A list of packages under the owner. + """ + packages("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Find packages by their names." names: [String], "Find packages in a repository by ID." repositoryId: ID, "Filter registry package by type." packageType: PackageType, "Ordering of the returned packages." orderBy: PackageOrder = { + field: CREATED_AT + direction: DESC + } + ): PackageConnection! + + """ + The repository parent, if this is a fork. + """ + parent: Repository + + """ + A list of discussions that have been pinned in this repository. + """ + pinnedDiscussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnedDiscussionConnection! + + """ + A list of pinned environments for this repository. + """ + pinnedEnvironments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the environments" orderBy: PinnedEnvironmentOrder = { + field: POSITION + direction: ASC + } + ): PinnedEnvironmentConnection + + """ + A list of pinned issues for this repository. + """ + pinnedIssues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnedIssueConnection + + """ + Returns information about the availability of certain features and limits based on the repository's billing plan. + """ + planFeatures: RepositoryPlanFeatures! + + """ + The primary language of the repository's code. + """ + primaryLanguage: Language + + """ + Find project by number. + """ + project("The project number to find." number: Int!): Project + + """ + Finds and returns the Project according to the provided Project number. + """ + projectV2("The Project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projects("Ordering options for projects returned from the connection" orderBy: ProjectOrder, "Query to search projects by, currently only searching by name." search: String, "A list of states to filter the projects by." states: [ProjectState!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectConnection! + + """ + The HTTP path listing the repository's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing the repository's projects + """ + projectsUrl: URI! + + """ + List of projects linked to this repository. + """ + projectsV2("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A project to search for linked to the repo." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + ): ProjectV2Connection! + + """ + Returns a single pull request from the current repository by number. + """ + pullRequest("The number for the pull request to be returned." number: Int!): PullRequest + + """ + Returns a list of pull request templates associated to the repository + """ + pullRequestTemplates: [PullRequestTemplate!] + + """ + A list of pull requests that have been opened in the repository. + """ + pullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection! + + """ + Identifies the date and time when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + Whether or not rebase-merging is enabled on this repository. + """ + rebaseMergeAllowed: Boolean! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + Fetch a given ref from the repository + """ + ref("The ref to retrieve. Fully qualified matches are checked in order (`refs\/heads\/master`) before falling back onto checks for short name matches (`master`)." qualifiedName: String!): Ref + + """ + Fetch a list of refs from the repository + """ + refs("Filters refs with query on name" query: String, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "A ref name prefix like `refs\/heads\/`, `refs\/tags\/`, etc." refPrefix: String!, "DEPRECATED: use orderBy. The ordering direction." direction: OrderDirection, "Ordering options for refs returned from the connection." orderBy: RefOrder): RefConnection + + """ + Lookup a single release given various criteria. + """ + release("The name of the Tag the Release was created from" tagName: String!): Release + + """ + List of releases which are dependent on this repository. + """ + releases("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: ReleaseOrder): ReleaseConnection! + + """ + A list of applied repository-topic associations for this repository. + """ + repositoryTopics("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryTopicConnection! + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + Returns a single ruleset from the current repository by ID. + """ + ruleset("Include rulesets configured at higher levels that apply to this repository" includeParents: Boolean = true, "The ID of the ruleset to be returned." databaseId: Int!): RepositoryRuleset + + """ + A list of rulesets for this repository. + """ + rulesets("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Return rulesets configured at higher levels that apply to this repository" includeParents: Boolean = true): RepositoryRulesetConnection + + """ + The security policy URL. + """ + securityPolicyUrl: URI + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML("How many characters to return." limit: Int = 200): HTML! + + """ + Whether or not squash-merging is enabled on this repository. + """ + squashMergeAllowed: Boolean! + + """ + How the default commit message will be generated when squash merging a pull request. + """ + squashMergeCommitMessage: SquashMergeCommitMessage! + + """ + How the default commit title will be generated when squash merging a pull request. + """ + squashMergeCommitTitle: SquashMergeCommitTitle! + + """ + Whether a squash merge commit can use the pull request title as default. + """ + squashPrTitleUsedAsDefault: Boolean! @deprecated(reason: "`squashPrTitleUsedAsDefault` will be removed. Use `Repository.squashMergeCommitTitle` instead. Removal on 2023-04-01 UTC.") + + """ + The SSH URL to clone this repository + """ + sshUrl: GitSSHRemote! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: StarOrder): StargazerConnection! + + """ + Returns a list of all submodules in this repository parsed from the .gitmodules file as of the default branch's HEAD commit. + """ + submodules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SubmoduleConnection! + + """ + Temporary authentication token for cloning this repository. + """ + tempCloneToken: String + + """ + The repository from which this repository was generated, if any. + """ + templateRepository: Repository + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! + + """ + Indicates whether the viewer has admin permissions on this repository. + """ + viewerCanAdminister: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Indicates whether the viewer can update the topics of this repository. + """ + viewerCanUpdateTopics: Boolean! + + """ + The last commit email for the viewer. + """ + viewerDefaultCommitEmail: String + + """ + The last used merge method by the viewer or the default for the repository. + """ + viewerDefaultMergeMethod: PullRequestMergeMethod! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! + + """ + The users permission level on the repository. Will return null if authenticated as an GitHub App. + """ + viewerPermission: RepositoryPermission + + """ + A list of emails this viewer can commit with. + """ + viewerPossibleCommitEmails: [String!] + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! + + """ + Returns a single vulnerability alert from the current repository by number. + """ + vulnerabilityAlert("The number for the vulnerability alert to be returned." number: Int!): RepositoryVulnerabilityAlert + + """ + A list of vulnerability alerts that are on this repository. + """ + vulnerabilityAlerts("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter by the state of the alert" states: [RepositoryVulnerabilityAlertState!], "Filter by the scope of the alert's dependency" dependencyScopes: [RepositoryVulnerabilityAlertDependencyScope!]): RepositoryVulnerabilityAlertConnection + + """ + A list of users watching the repository. + """ + watchers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserConnection! + + """ + Whether contributors are required to sign off on web-based commits in this repository. + """ + webCommitSignoffRequired: Boolean! +} + +""" +The affiliation of a user to a repository +""" +enum RepositoryAffiliation { + """ + Repositories that are owned by the authenticated user. + """ + OWNER + + """ + Repositories that the user has been added to as a collaborator. + """ + COLLABORATOR + + """ + Repositories that the user has access to through being a member of an organization. This includes every repository on every team that the user is on. + """ + ORGANIZATION_MEMBER +} + +""" +Metadata for an audit entry with action repo.* +""" +interface RepositoryAuditEntryData { + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI +} + +""" +Information extracted from a repository's `CODEOWNERS` file. +""" +type RepositoryCodeowners { + """ + Any problems that were encountered while parsing the `CODEOWNERS` file. + """ + errors: [RepositoryCodeownersError!]! +} + +""" +An error in a `CODEOWNERS` file. +""" +type RepositoryCodeownersError { + """ + The column number where the error occurs. + """ + column: Int! + + """ + A short string describing the type of error. + """ + kind: String! + + """ + The line number where the error occurs. + """ + line: Int! + + """ + A complete description of the error, combining information from other fields. + """ + message: String! + + """ + The path to the file when the error occurs. + """ + path: String! + + """ + The content of the line where the error occurs. + """ + source: String! + + """ + A suggestion of how to fix the error. + """ + suggestion: String +} + +""" +The connection type for User. +""" +type RepositoryCollaboratorConnection { + """ + A list of edges. + """ + edges: [RepositoryCollaboratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a collaborator of a repository. +""" +type RepositoryCollaboratorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + node: User! + + """ + The permission the user has on the repository. + """ + permission: RepositoryPermission! + + """ + A list of sources for the user's access to the repository. + """ + permissionSources: [PermissionSource!] +} + +""" +A list of repositories owned by the subject. +""" +type RepositoryConnection { + """ + A list of edges. + """ + edges: [RepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total size in kilobytes of all repositories in the connection. Value will never be larger than max 32-bit signed integer. + """ + totalDiskUsage: Int! +} + +""" +A repository contact link. +""" +type RepositoryContactLink { + """ + The contact link purpose. + """ + about: String! + + """ + The contact link name. + """ + name: String! + + """ + The contact link URL. + """ + url: URI! +} + +""" +The reason a repository is listed as 'contributed'. +""" +enum RepositoryContributionType { + """ + Created a commit + """ + COMMIT + + """ + Created an issue + """ + ISSUE + + """ + Created a pull request + """ + PULL_REQUEST + + """ + Created the repository + """ + REPOSITORY + + """ + Reviewed a pull request + """ + PULL_REQUEST_REVIEW +} + +""" +Represents an author of discussions in repositories. +""" +interface RepositoryDiscussionAuthor { + """ + Discussions this user has started. + """ + repositoryDiscussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for discussions returned from the connection." orderBy: DiscussionOrder = { + field: CREATED_AT + direction: DESC + } + , "Filter discussions to only those in a specific repository." repositoryId: ID, "Filter discussions to only those that have been answered or not. Defaults to including both answered and unanswered discussions." answered: Boolean = null, "A list of states to filter the discussions by." states: [DiscussionState!] = []): DiscussionConnection! +} + +""" +Represents an author of discussion comments in repositories. +""" +interface RepositoryDiscussionCommentAuthor { + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter discussion comments to only those in a specific repository." repositoryId: ID, "Filter discussion comments to only those that were marked as the answer" onlyAnswers: Boolean = false): DiscussionCommentConnection! +} + +""" +An edge in a connection. +""" +type RepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Repository +} + +""" +Parameters to be used for the repository_id condition +""" +type RepositoryIdConditionTarget { + """ + One of these repo IDs must match the repo. + """ + repositoryIds: [ID!]! +} + +""" +Parameters to be used for the repository_id condition +""" +input RepositoryIdConditionTargetInput { + """ + One of these repo IDs must match the repo. + """ + repositoryIds: [ID!]! +} + +""" +A subset of repository info. +""" +interface RepositoryInfo { + """ + Identifies the date and time when the repository was archived. + """ + archivedAt: DateTime + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the repository. + """ + description: String + + """ + The description of the repository rendered to HTML. + """ + descriptionHTML: HTML! + + """ + Returns how many forks there are of this repository in the whole network. + """ + forkCount: Int! + + """ + Indicates if the repository has the Discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean! + + """ + Indicates if the repository has issues feature enabled. + """ + hasIssuesEnabled: Boolean! + + """ + Indicates if the repository has the Projects feature enabled. + """ + hasProjectsEnabled: Boolean! + + """ + Indicates if the repository displays a Sponsor button for financial contributions. + """ + hasSponsorshipsEnabled: Boolean! + + """ + Indicates if the repository has wiki feature enabled. + """ + hasWikiEnabled: Boolean! + + """ + The repository's URL. + """ + homepageUrl: URI + + """ + Indicates if the repository is unmaintained. + """ + isArchived: Boolean! + + """ + Identifies if the repository is a fork. + """ + isFork: Boolean! + + """ + Indicates if a repository is either owned by an organization, or is a private fork of an organization repository. + """ + isInOrganization: Boolean! + + """ + Indicates if the repository has been locked or not. + """ + isLocked: Boolean! + + """ + Identifies if the repository is a mirror. + """ + isMirror: Boolean! + + """ + Identifies if the repository is private or internal. + """ + isPrivate: Boolean! + + """ + Identifies if the repository is a template that can be used to generate new repositories. + """ + isTemplate: Boolean! + + """ + The license associated with the repository + """ + licenseInfo: License + + """ + The reason the repository has been locked. + """ + lockReason: RepositoryLockReason + + """ + The repository's original mirror URL. + """ + mirrorUrl: URI + + """ + The name of the repository. + """ + name: String! + + """ + The repository's name with owner. + """ + nameWithOwner: String! + + """ + The image used to represent this repository in Open Graph data. + """ + openGraphImageUrl: URI! + + """ + The User owner of the repository. + """ + owner: RepositoryOwner! + + """ + Identifies the date and time when the repository was last pushed to. + """ + pushedAt: DateTime + + """ + The HTTP path for this repository + """ + resourcePath: URI! + + """ + A description of the repository, rendered to HTML without any links in it. + """ + shortDescriptionHTML("How many characters to return." limit: Int = 200): HTML! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this repository + """ + url: URI! + + """ + Whether this repository has a custom image to use with Open Graph as opposed to being represented by the owner's avatar. + """ + usesCustomOpenGraphImage: Boolean! + + """ + Indicates the repository's visibility level. + """ + visibility: RepositoryVisibility! +} + +""" +Repository interaction limit that applies to this object. +""" +type RepositoryInteractionAbility { + """ + The time the currently active limit expires. + """ + expiresAt: DateTime + + """ + The current limit that is enabled on this object. + """ + limit: RepositoryInteractionLimit! + + """ + The origin of the currently active interaction limit. + """ + origin: RepositoryInteractionLimitOrigin! +} + +""" +A repository interaction limit. +""" +enum RepositoryInteractionLimit { + """ + Users that have recently created their account will be unable to interact with the repository. + """ + EXISTING_USERS + + """ + Users that have not previously committed to a repository’s default branch will be unable to interact with the repository. + """ + CONTRIBUTORS_ONLY + + """ + Users that are not collaborators will not be able to interact with the repository. + """ + COLLABORATORS_ONLY + + """ + No interaction limits are enabled. + """ + NO_LIMIT +} + +""" +The length for a repository interaction limit to be enabled for. +""" +enum RepositoryInteractionLimitExpiry { + """ + The interaction limit will expire after 1 day. + """ + ONE_DAY + + """ + The interaction limit will expire after 3 days. + """ + THREE_DAYS + + """ + The interaction limit will expire after 1 week. + """ + ONE_WEEK + + """ + The interaction limit will expire after 1 month. + """ + ONE_MONTH + + """ + The interaction limit will expire after 6 months. + """ + SIX_MONTHS +} + +""" +Indicates where an interaction limit is configured. +""" +enum RepositoryInteractionLimitOrigin { + """ + A limit that is configured at the repository level. + """ + REPOSITORY + + """ + A limit that is configured at the organization level. + """ + ORGANIZATION + + """ + A limit that is configured at the user-wide level. + """ + USER +} + +""" +An invitation for a user to be added to a repository. +""" +type RepositoryInvitation implements Node { + """ + The email address that received the invitation. + """ + email: String + + """ + The Node ID of the RepositoryInvitation object + """ + id: ID! + + """ + The user who received the invitation. + """ + invitee: User + + """ + The user who created the invitation. + """ + inviter: User! + + """ + The permalink for this repository invitation. + """ + permalink: URI! + + """ + The permission granted on this repository by this invitation. + """ + permission: RepositoryPermission! + + """ + The Repository the user is invited to. + """ + repository: RepositoryInfo +} + +""" +A list of repository invitations. +""" +type RepositoryInvitationConnection { + """ + A list of edges. + """ + edges: [RepositoryInvitationEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryInvitation] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryInvitationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryInvitation +} + +""" +Ordering options for repository invitation connections. +""" +input RepositoryInvitationOrder { + """ + The field to order repository invitations by. + """ + field: RepositoryInvitationOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which repository invitation connections can be ordered. +""" +enum RepositoryInvitationOrderField { + """ + Order repository invitations by creation time + """ + CREATED_AT +} + +""" +The possible reasons a given repository could be in a locked state. +""" +enum RepositoryLockReason { + """ + The repository is locked due to a move. + """ + MOVING + + """ + The repository is locked due to a billing related reason. + """ + BILLING + + """ + The repository is locked due to a rename. + """ + RENAME + + """ + The repository is locked due to a migration. + """ + MIGRATING + + """ + The repository is locked due to a trade controls related reason. + """ + TRADE_RESTRICTION + + """ + The repository is locked due to an ownership transfer. + """ + TRANSFERRING_OWNERSHIP +} + +""" +A GitHub Enterprise Importer (GEI) repository migration. +""" +type RepositoryMigration implements Migration & Node { + """ + The migration flag to continue on error. + """ + continueOnError: Boolean! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: String + + """ + The reason the migration failed. + """ + failureReason: String + + """ + The Node ID of the RepositoryMigration object + """ + id: ID! + + """ + The URL for the migration log (expires 1 day after migration completes). + """ + migrationLogUrl: URI + + """ + The migration source. + """ + migrationSource: MigrationSource! + + """ + The target repository name. + """ + repositoryName: String! + + """ + The migration source URL, for example `https://github.com` or `https://monalisa.ghe.com`. + """ + sourceUrl: URI! + + """ + The migration state. + """ + state: MigrationState! + + """ + The number of warnings encountered for this migration. To review the warnings, check the [Migration Log](https://docs.github.com/migrations/using-github-enterprise-importer/completing-your-migration-with-github-enterprise-importer/accessing-your-migration-logs-for-github-enterprise-importer). + """ + warningsCount: Int! +} + +""" +A list of migrations. +""" +type RepositoryMigrationConnection { + """ + A list of edges. + """ + edges: [RepositoryMigrationEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryMigration] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a repository migration. +""" +type RepositoryMigrationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryMigration +} + +""" +Ordering options for repository migrations. +""" +input RepositoryMigrationOrder { + """ + The field to order repository migrations by. + """ + field: RepositoryMigrationOrderField! + + """ + The ordering direction. + """ + direction: RepositoryMigrationOrderDirection! +} + +""" +Possible directions in which to order a list of repository migrations when provided an `orderBy` argument. +""" +enum RepositoryMigrationOrderDirection { + """ + Specifies an ascending order for a given `orderBy` argument. + """ + ASC + + """ + Specifies a descending order for a given `orderBy` argument. + """ + DESC +} + +""" +Properties by which repository migrations can be ordered. +""" +enum RepositoryMigrationOrderField { + """ + Order mannequins why when they were created. + """ + CREATED_AT +} + +""" +Parameters to be used for the repository_name condition +""" +type RepositoryNameConditionTarget { + """ + Array of repository names or patterns to exclude. The condition will not pass if any of these patterns match. + """ + exclude: [String!]! + + """ + Array of repository names or patterns to include. One of these patterns must match for the condition to pass. Also accepts `~ALL` to include all repositories. + """ + include: [String!]! + + """ + Target changes that match these patterns will be prevented except by those with bypass permissions. + """ + protected: Boolean! +} + +""" +Parameters to be used for the repository_name condition +""" +input RepositoryNameConditionTargetInput { + """ + Array of repository names or patterns to exclude. The condition will not pass if any of these patterns match. + """ + exclude: [String!]! + + """ + Array of repository names or patterns to include. One of these patterns must match for the condition to pass. Also accepts `~ALL` to include all repositories. + """ + include: [String!]! + + """ + Target changes that match these patterns will be prevented except by those with bypass permissions. + """ + protected: Boolean +} + +""" +Represents a object that belongs to a repository. +""" +interface RepositoryNode { + """ + The repository associated with this node. + """ + repository: Repository! +} + +""" +Ordering options for repository connections +""" +input RepositoryOrder { + """ + The field to order repositories by. + """ + field: RepositoryOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which repository connections can be ordered. +""" +enum RepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by update time + """ + UPDATED_AT + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by number of stargazers + """ + STARGAZERS +} + +""" +Represents an owner of a Repository. +""" +interface RepositoryOwner { + """ + A URL pointing to the owner's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The Node ID of the RepositoryOwner object + """ + id: ID! + + """ + The username used to login. + """ + login: String! + + """ + A list of repositories that the user owns. + """ + repositories("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Array of viewer's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the current viewer owns." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If non-null, filters repositories according to whether they are archived and not maintained" isArchived: Boolean, "If non-null, filters repositories according to whether they are forks of another repository" isFork: Boolean): RepositoryConnection! + + """ + Find Repository. + """ + repository("Name of Repository to find." name: String!, "Follow repository renames. If disabled, a repository referenced by its old name will return an error." followRenames: Boolean = true): Repository + + """ + The HTTP URL for the owner. + """ + resourcePath: URI! + + """ + The HTTP URL for the owner. + """ + url: URI! +} + +""" +The access level to a repository +""" +enum RepositoryPermission { + """ + Can read, clone, and push to this repository. Can also manage issues, pull requests, and repository settings, including adding collaborators + """ + ADMIN + + """ + Can read, clone, and push to this repository. They can also manage issues, pull requests, and some repository settings + """ + MAINTAIN + + """ + Can read, clone, and push to this repository. Can also manage issues and pull requests + """ + WRITE + + """ + Can read and clone this repository. Can also manage issues and pull requests + """ + TRIAGE + + """ + Can read and clone this repository. Can also open and comment on issues and pull requests + """ + READ +} + +""" +Information about the availability of features and limits for a repository based on its billing plan. +""" +type RepositoryPlanFeatures { + """ + Whether reviews can be automatically requested and enforced with a CODEOWNERS file + """ + codeowners: Boolean! + + """ + Whether pull requests can be created as or converted to draft + """ + draftPullRequests: Boolean! + + """ + Maximum number of users that can be assigned to an issue or pull request + """ + maximumAssignees: Int! + + """ + Maximum number of manually-requested reviews on a pull request + """ + maximumManualReviewRequests: Int! + + """ + Whether teams can be requested to review pull requests + """ + teamReviewRequests: Boolean! +} + +""" +The privacy of a repository +""" +enum RepositoryPrivacy { + """ + Public + """ + PUBLIC + + """ + Private + """ + PRIVATE +} + +""" +Parameters to be used for the repository_property condition +""" +type RepositoryPropertyConditionTarget { + """ + Array of repository properties that must not match. + """ + exclude: [PropertyTargetDefinition!]! + + """ + Array of repository properties that must match + """ + include: [PropertyTargetDefinition!]! +} + +""" +Parameters to be used for the repository_property condition +""" +input RepositoryPropertyConditionTargetInput { + """ + Array of repository properties that must not match. + """ + exclude: [PropertyTargetDefinitionInput!]! + + """ + Array of repository properties that must match + """ + include: [PropertyTargetDefinitionInput!]! +} + +""" +A repository rule. +""" +type RepositoryRule implements Node { + """ + The Node ID of the RepositoryRule object + """ + id: ID! + + """ + The parameters for this rule. + """ + parameters: RuleParameters + + """ + The repository ruleset associated with this rule configuration + """ + repositoryRuleset: RepositoryRuleset + + """ + The type of rule. + """ + type: RepositoryRuleType! +} + +""" +Set of conditions that determine if a ruleset will evaluate +""" +type RepositoryRuleConditions { + """ + Configuration for the ref_name condition + """ + refName: RefNameConditionTarget + + """ + Configuration for the repository_id condition + """ + repositoryId: RepositoryIdConditionTarget + + """ + Configuration for the repository_name condition + """ + repositoryName: RepositoryNameConditionTarget + + """ + Configuration for the repository_property condition + """ + repositoryProperty: RepositoryPropertyConditionTarget +} + +""" +Specifies the conditions required for a ruleset to evaluate +""" +input RepositoryRuleConditionsInput { + """ + Configuration for the ref_name condition + """ + refName: RefNameConditionTargetInput + + """ + Configuration for the repository_name condition + """ + repositoryName: RepositoryNameConditionTargetInput + + """ + Configuration for the repository_id condition + """ + repositoryId: RepositoryIdConditionTargetInput + + """ + Configuration for the repository_property condition + """ + repositoryProperty: RepositoryPropertyConditionTargetInput +} + +""" +The connection type for RepositoryRule. +""" +type RepositoryRuleConnection { + """ + A list of edges. + """ + edges: [RepositoryRuleEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryRule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryRuleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryRule +} + +""" +Specifies the attributes for a new or updated rule. +""" +input RepositoryRuleInput { + """ + Optional ID of this rule when updating + """ + id: ID + + """ + The type of rule to create. + """ + type: RepositoryRuleType! + + """ + The parameters for the rule. + """ + parameters: RuleParametersInput +} + +""" +Ordering options for repository rules. +""" +input RepositoryRuleOrder { + """ + The field to order repository rules by. + """ + field: RepositoryRuleOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which repository rule connections can be ordered. +""" +enum RepositoryRuleOrderField { + """ + Order repository rules by updated time + """ + UPDATED_AT + + """ + Order repository rules by created time + """ + CREATED_AT + + """ + Order repository rules by type + """ + TYPE +} + +""" +The rule types supported in rulesets +""" +enum RepositoryRuleType { + """ + Only allow users with bypass permission to create matching refs. + """ + CREATION + + """ + Only allow users with bypass permission to update matching refs. + """ + UPDATE + + """ + Only allow users with bypass permissions to delete matching refs. + """ + DELETION + + """ + Prevent merge commits from being pushed to matching refs. + """ + REQUIRED_LINEAR_HISTORY + + """ + Merges must be performed via a merge queue. + """ + MERGE_QUEUE + + """ + When enabled, all conversations on code must be resolved before a pull request can be merged into a branch that matches this rule. + """ + REQUIRED_REVIEW_THREAD_RESOLUTION + + """ + Choose which environments must be successfully deployed to before refs can be pushed into a ref that matches this rule. + """ + REQUIRED_DEPLOYMENTS + + """ + Commits pushed to matching refs must have verified signatures. + """ + REQUIRED_SIGNATURES + + """ + Require all commits be made to a non-target branch and submitted via a pull request before they can be merged. + """ + PULL_REQUEST + + """ + Choose which status checks must pass before the ref is updated. When enabled, commits must first be pushed to another ref where the checks pass. + """ + REQUIRED_STATUS_CHECKS + + """ + Require all commits be made to a non-target branch and submitted via a pull request and required workflow checks to pass before they can be merged. + """ + REQUIRED_WORKFLOW_STATUS_CHECKS + + """ + Prevent users with push access from force pushing to refs. + """ + NON_FAST_FORWARD + + """ + Authorization + """ + AUTHORIZATION + + """ + Tag + """ + TAG + + """ + Merge queue locked ref + """ + MERGE_QUEUE_LOCKED_REF + + """ + Branch is read-only. Users cannot push to the branch. + """ + LOCK_BRANCH + + """ + Max ref updates + """ + MAX_REF_UPDATES + + """ + Commit message pattern + """ + COMMIT_MESSAGE_PATTERN + + """ + Commit author email pattern + """ + COMMIT_AUTHOR_EMAIL_PATTERN + + """ + Committer email pattern + """ + COMMITTER_EMAIL_PATTERN + + """ + Branch name pattern + """ + BRANCH_NAME_PATTERN + + """ + Tag name pattern + """ + TAG_NAME_PATTERN + + """ + Prevent commits that include changes in specified file paths from being pushed to the commit graph. NOTE: Thie rule is in beta and subject to change + """ + FILE_PATH_RESTRICTION + + """ + Prevent commits that include file paths that exceed a specified character limit from being pushed to the commit graph. NOTE: Thie rule is in beta and subject to change + """ + MAX_FILE_PATH_LENGTH + + """ + Prevent commits that include files with specified file extensions from being pushed to the commit graph. NOTE: Thie rule is in beta and subject to change + """ + FILE_EXTENSION_RESTRICTION + + """ + Prevent commits that exceed a specified file size limit from being pushed to the commit. NOTE: Thie rule is in beta and subject to change + """ + MAX_FILE_SIZE + + """ + Require all changes made to a targeted branch to pass the specified workflows before they can be merged. + """ + WORKFLOWS + + """ + Secret scanning + """ + SECRET_SCANNING + + """ + Workflow files cannot be modified. + """ + WORKFLOW_UPDATES + + """ + Choose which tools must provide code scanning results before the reference is updated. When configured, code scanning must be enabled and have results for both the commit and the reference being updated. + """ + CODE_SCANNING +} + +""" +A repository ruleset. +""" +type RepositoryRuleset implements Node { + """ + The actors that can bypass this ruleset + """ + bypassActors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryRulesetBypassActorConnection + + """ + The set of conditions that must evaluate to true for this ruleset to apply + """ + conditions: RepositoryRuleConditions! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The enforcement level of this ruleset + """ + enforcement: RuleEnforcement! + + """ + The Node ID of the RepositoryRuleset object + """ + id: ID! + + """ + Name of the ruleset. + """ + name: String! + + """ + List of rules. + """ + rules("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The type of rule." type: RepositoryRuleType): RepositoryRuleConnection + + """ + Source of ruleset. + """ + source: RuleSource! + + """ + Target of the ruleset. + """ + target: RepositoryRulesetTarget + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +A team or app that has the ability to bypass a rules defined on a ruleset +""" +type RepositoryRulesetBypassActor implements Node { + """ + The actor that can bypass rules. + """ + actor: BypassActor + + """ + The mode for the bypass actor + """ + bypassMode: RepositoryRulesetBypassActorBypassMode + + """ + This actor represents the ability for a deploy key to bypass + """ + deployKey: Boolean! + + """ + The Node ID of the RepositoryRulesetBypassActor object + """ + id: ID! + + """ + This actor represents the ability for an organization owner to bypass + """ + organizationAdmin: Boolean! + + """ + If the actor is a repository role, the repository role's ID that can bypass + """ + repositoryRoleDatabaseId: Int + + """ + If the actor is a repository role, the repository role's name that can bypass + """ + repositoryRoleName: String + + """ + Identifies the ruleset associated with the allowed actor + """ + repositoryRuleset: RepositoryRuleset +} + +""" +The bypass mode for a specific actor on a ruleset. +""" +enum RepositoryRulesetBypassActorBypassMode { + """ + The actor can always bypass rules + """ + ALWAYS + + """ + The actor can only bypass rules via a pull request + """ + PULL_REQUEST +} + +""" +The connection type for RepositoryRulesetBypassActor. +""" +type RepositoryRulesetBypassActorConnection { + """ + A list of edges. + """ + edges: [RepositoryRulesetBypassActorEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryRulesetBypassActor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryRulesetBypassActorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryRulesetBypassActor +} + +""" +Specifies the attributes for a new or updated ruleset bypass actor. Only one of `actor_id`, `repository_role_database_id`, `organization_admin`, or `deploy_key` should be specified. +""" +input RepositoryRulesetBypassActorInput { + """ + For Team and Integration bypasses, the Team or Integration ID + """ + actorId: ID + + """ + For role bypasses, the role database ID + """ + repositoryRoleDatabaseId: Int + + """ + For organization owner bypasses, true + """ + organizationAdmin: Boolean + + """ + For deploy key bypasses, true. Can only use ALWAYS as the bypass mode + """ + deployKey: Boolean + + """ + The bypass mode for this actor. + """ + bypassMode: RepositoryRulesetBypassActorBypassMode! +} + +""" +The connection type for RepositoryRuleset. +""" +type RepositoryRulesetConnection { + """ + A list of edges. + """ + edges: [RepositoryRulesetEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryRuleset] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryRulesetEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryRuleset +} + +""" +The targets supported for rulesets. NOTE: The push target is in beta and subject to change. +""" +enum RepositoryRulesetTarget { + """ + Branch + """ + BRANCH + + """ + Tag + """ + TAG + + """ + Push + """ + PUSH +} + +""" +A repository-topic connects a repository to a topic. +""" +type RepositoryTopic implements Node & UniformResourceLocatable { + """ + The Node ID of the RepositoryTopic object + """ + id: ID! + + """ + The HTTP path for this repository-topic. + """ + resourcePath: URI! + + """ + The topic. + """ + topic: Topic! + + """ + The HTTP URL for this repository-topic. + """ + url: URI! +} + +""" +The connection type for RepositoryTopic. +""" +type RepositoryTopicConnection { + """ + A list of edges. + """ + edges: [RepositoryTopicEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryTopic] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RepositoryTopicEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryTopic +} + +""" +The repository's visibility level. +""" +enum RepositoryVisibility { + """ + The repository is visible only to those with explicit access. + """ + PRIVATE + + """ + The repository is visible to everyone. + """ + PUBLIC + + """ + The repository is visible only to users in the same business. + """ + INTERNAL +} + +""" +Audit log entry for a repository_visibility_change.disable event. +""" +type RepositoryVisibilityChangeDisableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the RepositoryVisibilityChangeDisableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a repository_visibility_change.enable event. +""" +type RepositoryVisibilityChangeEnableAuditEntry implements AuditEntry & EnterpriseAuditEntryData & Node & OrganizationAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The HTTP path for this enterprise. + """ + enterpriseResourcePath: URI + + """ + The slug of the enterprise. + """ + enterpriseSlug: String + + """ + The HTTP URL for this enterprise. + """ + enterpriseUrl: URI + + """ + The Node ID of the RepositoryVisibilityChangeEnableAuditEntry object + """ + id: ID! + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +A Dependabot alert for a repository with a dependency affected by a security vulnerability. +""" +type RepositoryVulnerabilityAlert implements Node & RepositoryNode { + """ + When was the alert auto-dismissed? + """ + autoDismissedAt: DateTime + + """ + When was the alert created? + """ + createdAt: DateTime! + + """ + The associated Dependabot update + """ + dependabotUpdate: DependabotUpdate + + """ + The scope of an alert's dependency + """ + dependencyScope: RepositoryVulnerabilityAlertDependencyScope + + """ + Comment explaining the reason the alert was dismissed + """ + dismissComment: String + + """ + The reason the alert was dismissed + """ + dismissReason: String + + """ + When was the alert dismissed? + """ + dismissedAt: DateTime + + """ + The user who dismissed the alert + """ + dismisser: User + + """ + When was the alert fixed? + """ + fixedAt: DateTime + + """ + The Node ID of the RepositoryVulnerabilityAlert object + """ + id: ID! + + """ + Identifies the alert number. + """ + number: Int! + + """ + The associated repository + """ + repository: Repository! + + """ + The associated security advisory + """ + securityAdvisory: SecurityAdvisory + + """ + The associated security vulnerability + """ + securityVulnerability: SecurityVulnerability + + """ + Identifies the state of the alert. + """ + state: RepositoryVulnerabilityAlertState! + + """ + The vulnerable manifest filename + """ + vulnerableManifestFilename: String! + + """ + The vulnerable manifest path + """ + vulnerableManifestPath: String! + + """ + The vulnerable requirements + """ + vulnerableRequirements: String +} + +""" +The connection type for RepositoryVulnerabilityAlert. +""" +type RepositoryVulnerabilityAlertConnection { + """ + A list of edges. + """ + edges: [RepositoryVulnerabilityAlertEdge] + + """ + A list of nodes. + """ + nodes: [RepositoryVulnerabilityAlert] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible scopes of an alert's dependency. +""" +enum RepositoryVulnerabilityAlertDependencyScope { + """ + A dependency that is leveraged during application runtime + """ + RUNTIME + + """ + A dependency that is only used in development + """ + DEVELOPMENT +} + +""" +An edge in a connection. +""" +type RepositoryVulnerabilityAlertEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RepositoryVulnerabilityAlert +} + +""" +The possible states of an alert +""" +enum RepositoryVulnerabilityAlertState { + """ + An alert that is still open. + """ + OPEN + + """ + An alert that has been resolved by a code change. + """ + FIXED + + """ + An alert that has been manually closed by a user. + """ + DISMISSED + + """ + An alert that has been automatically closed by Dependabot. + """ + AUTO_DISMISSED +} + +""" +Autogenerated input type of RequestReviews +""" +input RequestReviewsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request to modify. + """ + pullRequestId: ID! + + """ + The Node IDs of the user to request. + """ + userIds: [ID!] + + """ + The Node IDs of the team to request. + """ + teamIds: [ID!] + + """ + Add users to the set rather than replace. + """ + union: Boolean = false +} + +""" +Autogenerated return type of RequestReviews. +""" +type RequestReviewsPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that is getting requests. + """ + pullRequest: PullRequest + + """ + The edge from the pull request to the requested reviewers. + """ + requestedReviewersEdge: UserEdge +} + +""" +The possible states that can be requested when creating a check run. +""" +enum RequestableCheckStatusState { + """ + The check suite or run has been queued. + """ + QUEUED + + """ + The check suite or run is in progress. + """ + IN_PROGRESS + + """ + The check suite or run has been completed. + """ + COMPLETED + + """ + The check suite or run is in waiting state. + """ + WAITING + + """ + The check suite or run is in pending state. + """ + PENDING +} + +""" +Types that can be requested reviewers. +""" +union RequestedReviewer = Bot|Mannequin|Team|User + +""" +The connection type for RequestedReviewer. +""" +type RequestedReviewerConnection { + """ + A list of edges. + """ + edges: [RequestedReviewerEdge] + + """ + A list of nodes. + """ + nodes: [RequestedReviewer] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type RequestedReviewerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: RequestedReviewer +} + +""" +Represents a type that can be required by a pull request for merging. +""" +interface RequirableByPullRequest { + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired("The id of the pull request this is required for" pullRequestId: ID, "The number of the pull request this is required for" pullRequestNumber: Int): Boolean! +} + +""" +Choose which environments must be successfully deployed to before refs can be pushed into a ref that matches this rule. +""" +type RequiredDeploymentsParameters { + """ + The environments that must be successfully deployed to before branches can be merged. + """ + requiredDeploymentEnvironments: [String!]! +} + +""" +Choose which environments must be successfully deployed to before refs can be pushed into a ref that matches this rule. +""" +input RequiredDeploymentsParametersInput { + """ + The environments that must be successfully deployed to before branches can be merged. + """ + requiredDeploymentEnvironments: [String!]! +} + +""" +Represents a required status check for a protected branch, but not any specific run of that check. +""" +type RequiredStatusCheckDescription { + """ + The App that must provide this status in order for it to be accepted. + """ + app: App + + """ + The name of this status. + """ + context: String! +} + +""" +Specifies the attributes for a new or updated required status check. +""" +input RequiredStatusCheckInput { + """ + Status check context that must pass for commits to be accepted to the matching branch. + """ + context: String! + + """ + The ID of the App that must set the status in order for it to be accepted. Omit this value to use whichever app has recently been setting this status, or use "any" to allow any app to set the status. + """ + appId: ID +} + +""" +Choose which status checks must pass before the ref is updated. When enabled, commits must first be pushed to another ref where the checks pass. +""" +type RequiredStatusChecksParameters { + """ + Status checks that are required. + """ + requiredStatusChecks: [StatusCheckConfiguration!]! + + """ + Whether pull requests targeting a matching branch must be tested with the latest code. This setting will not take effect unless at least one status check is enabled. + """ + strictRequiredStatusChecksPolicy: Boolean! +} + +""" +Choose which status checks must pass before the ref is updated. When enabled, commits must first be pushed to another ref where the checks pass. +""" +input RequiredStatusChecksParametersInput { + """ + Status checks that are required. + """ + requiredStatusChecks: [StatusCheckConfigurationInput!]! + + """ + Whether pull requests targeting a matching branch must be tested with the latest code. This setting will not take effect unless at least one status check is enabled. + """ + strictRequiredStatusChecksPolicy: Boolean! +} + +""" +Autogenerated input type of RerequestCheckSuite +""" +input RerequestCheckSuiteInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + The Node ID of the check suite. + """ + checkSuiteId: ID! +} + +""" +Autogenerated return type of RerequestCheckSuite. +""" +type RerequestCheckSuitePayload { + """ + The requested check suite. + """ + checkSuite: CheckSuite + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of ResolveReviewThread +""" +input ResolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to resolve + """ + threadId: ID! +} + +""" +Autogenerated return type of ResolveReviewThread. +""" +type ResolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Represents a private contribution a user made on GitHub. +""" +type RestrictedContribution implements Contribution { + """ + Whether this contribution is associated with a record you do not have access to. For + example, your own 'first issue' contribution may have been made on a repository you can no + longer access. + """ + isRestricted: Boolean! + + """ + When this contribution was made. + """ + occurredAt: DateTime! + + """ + The HTTP path for this contribution. + """ + resourcePath: URI! + + """ + The HTTP URL for this contribution. + """ + url: URI! + + """ + The user who made this contribution. + """ + user: User! +} + +""" +Autogenerated input type of RetireSponsorsTier +""" +input RetireSponsorsTierInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the published tier to retire. + """ + tierId: ID! +} + +""" +Autogenerated return type of RetireSponsorsTier. +""" +type RetireSponsorsTierPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The tier that was retired. + """ + sponsorsTier: SponsorsTier +} + +""" +Autogenerated input type of RevertPullRequest +""" +input RevertPullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the pull request to revert. + """ + pullRequestId: ID! + + """ + The title of the revert pull request. + """ + title: String + + """ + The description of the revert pull request. + """ + body: String + + """ + Indicates whether the revert pull request should be a draft. + """ + draft: Boolean = false +} + +""" +Autogenerated return type of RevertPullRequest. +""" +type RevertPullRequestPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The pull request that was reverted. + """ + pullRequest: PullRequest + + """ + The new pull request that reverts the input pull request. + """ + revertPullRequest: PullRequest +} + +""" +A user, team, or app who has the ability to dismiss a review on a protected branch. +""" +type ReviewDismissalAllowance implements Node { + """ + The actor that can dismiss. + """ + actor: ReviewDismissalAllowanceActor + + """ + Identifies the branch protection rule associated with the allowed user, team, or app. + """ + branchProtectionRule: BranchProtectionRule + + """ + The Node ID of the ReviewDismissalAllowance object + """ + id: ID! +} + +""" +Types that can be an actor. +""" +union ReviewDismissalAllowanceActor = App|Team|User + +""" +The connection type for ReviewDismissalAllowance. +""" +type ReviewDismissalAllowanceConnection { + """ + A list of edges. + """ + edges: [ReviewDismissalAllowanceEdge] + + """ + A list of nodes. + """ + nodes: [ReviewDismissalAllowance] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewDismissalAllowanceEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewDismissalAllowance +} + +""" +Represents a 'review_dismissed' event on a given issue or pull request. +""" +type ReviewDismissedEvent implements Node & UniformResourceLocatable { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + Identifies the optional message associated with the 'review_dismissed' event. + """ + dismissalMessage: String + + """ + Identifies the optional message associated with the event, rendered to HTML. + """ + dismissalMessageHTML: String + + """ + The Node ID of the ReviewDismissedEvent object + """ + id: ID! + + """ + Identifies the previous state of the review with the 'review_dismissed' event. + """ + previousReviewState: PullRequestReviewState! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the commit which caused the review to become stale. + """ + pullRequestCommit: PullRequestCommit + + """ + The HTTP path for this review dismissed event. + """ + resourcePath: URI! + + """ + Identifies the review associated with the 'review_dismissed' event. + """ + review: PullRequestReview + + """ + The HTTP URL for this review dismissed event. + """ + url: URI! +} + +""" +A request for a user to review a pull request. +""" +type ReviewRequest implements Node { + """ + Whether this request was created for a code owner + """ + asCodeOwner: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the ReviewRequest object + """ + id: ID! + + """ + Identifies the pull request associated with this review request. + """ + pullRequest: PullRequest! + + """ + The reviewer that is requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +The connection type for ReviewRequest. +""" +type ReviewRequestConnection { + """ + A list of edges. + """ + edges: [ReviewRequestEdge] + + """ + A list of nodes. + """ + nodes: [ReviewRequest] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type ReviewRequestEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ReviewRequest +} + +""" +Represents an 'review_request_removed' event on a given pull request. +""" +type ReviewRequestRemovedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ReviewRequestRemovedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review request was removed. + """ + requestedReviewer: RequestedReviewer +} + +""" +Represents an 'review_requested' event on a given pull request. +""" +type ReviewRequestedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the ReviewRequestedEvent object + """ + id: ID! + + """ + PullRequest referenced by event. + """ + pullRequest: PullRequest! + + """ + Identifies the reviewer whose review was requested. + """ + requestedReviewer: RequestedReviewer +} + +""" +A hovercard context with a message describing the current code review state of the pull +request. +""" +type ReviewStatusHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + The current status of the pull request with respect to code review. + """ + reviewDecision: PullRequestReviewDecision +} + +""" +Autogenerated input type of RevokeEnterpriseOrganizationsMigratorRole +""" +input RevokeEnterpriseOrganizationsMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise to which all organizations managed by it will be granted the migrator role. + """ + enterpriseId: ID! + + """ + The login of the user to revoke the migrator role + """ + login: String! +} + +""" +Autogenerated return type of RevokeEnterpriseOrganizationsMigratorRole. +""" +type RevokeEnterpriseOrganizationsMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organizations that had the migrator role revoked for the given user. + """ + organizations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationConnection +} + +""" +Autogenerated input type of RevokeMigratorRole +""" +input RevokeMigratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization that the user/team belongs to. + """ + organizationId: ID! + + """ + The user login or Team slug to revoke the migrator role from. + """ + actor: String! + + """ + Specifies the type of the actor, can be either USER or TEAM. + """ + actorType: ActorType! +} + +""" +Autogenerated return type of RevokeMigratorRole. +""" +type RevokeMigratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Possible roles a user may have in relation to an organization. +""" +enum RoleInOrganization { + """ + A user with full administrative access to the organization. + """ + OWNER + + """ + A user who is a direct member of the organization. + """ + DIRECT_MEMBER + + """ + A user who is unaffiliated with the organization. + """ + UNAFFILIATED +} + +""" +The level of enforcement for a rule or ruleset. +""" +enum RuleEnforcement { + """ + Do not evaluate or enforce rules + """ + DISABLED + + """ + Rules will be enforced + """ + ACTIVE + + """ + Allow admins to test rules before enforcing them. Admins can view insights on the Rule Insights page (`evaluate` is only available with GitHub Enterprise). + """ + EVALUATE +} + +""" +Types which can be parameters for `RepositoryRule` objects. +""" +union RuleParameters = BranchNamePatternParameters|CodeScanningParameters|CommitAuthorEmailPatternParameters|CommitMessagePatternParameters|CommitterEmailPatternParameters|FileExtensionRestrictionParameters|FilePathRestrictionParameters|MaxFilePathLengthParameters|MaxFileSizeParameters|PullRequestParameters|RequiredDeploymentsParameters|RequiredStatusChecksParameters|TagNamePatternParameters|UpdateParameters|WorkflowsParameters + +""" +Specifies the parameters for a `RepositoryRule` object. Only one of the fields should be specified. +""" +input RuleParametersInput { + """ + Parameters used for the `update` rule type + """ + update: UpdateParametersInput + + """ + Parameters used for the `required_deployments` rule type + """ + requiredDeployments: RequiredDeploymentsParametersInput + + """ + Parameters used for the `pull_request` rule type + """ + pullRequest: PullRequestParametersInput + + """ + Parameters used for the `required_status_checks` rule type + """ + requiredStatusChecks: RequiredStatusChecksParametersInput + + """ + Parameters used for the `commit_message_pattern` rule type + """ + commitMessagePattern: CommitMessagePatternParametersInput + + """ + Parameters used for the `commit_author_email_pattern` rule type + """ + commitAuthorEmailPattern: CommitAuthorEmailPatternParametersInput + + """ + Parameters used for the `committer_email_pattern` rule type + """ + committerEmailPattern: CommitterEmailPatternParametersInput + + """ + Parameters used for the `branch_name_pattern` rule type + """ + branchNamePattern: BranchNamePatternParametersInput + + """ + Parameters used for the `tag_name_pattern` rule type + """ + tagNamePattern: TagNamePatternParametersInput + + """ + Parameters used for the `file_path_restriction` rule type + """ + filePathRestriction: FilePathRestrictionParametersInput + + """ + Parameters used for the `max_file_path_length` rule type + """ + maxFilePathLength: MaxFilePathLengthParametersInput + + """ + Parameters used for the `file_extension_restriction` rule type + """ + fileExtensionRestriction: FileExtensionRestrictionParametersInput + + """ + Parameters used for the `max_file_size` rule type + """ + maxFileSize: MaxFileSizeParametersInput + + """ + Parameters used for the `workflows` rule type + """ + workflows: WorkflowsParametersInput + + """ + Parameters used for the `code_scanning` rule type + """ + codeScanning: CodeScanningParametersInput +} + +""" +Types which can have `RepositoryRule` objects. +""" +union RuleSource = Organization|Repository + +""" +The possible digest algorithms used to sign SAML requests for an identity provider. +""" +enum SamlDigestAlgorithm { + """ + SHA1 + """ + SHA1 + + """ + SHA256 + """ + SHA256 + + """ + SHA384 + """ + SHA384 + + """ + SHA512 + """ + SHA512 +} + +""" +The possible signature algorithms used to sign SAML requests for a Identity Provider. +""" +enum SamlSignatureAlgorithm { + """ + RSA-SHA1 + """ + RSA_SHA1 + + """ + RSA-SHA256 + """ + RSA_SHA256 + + """ + RSA-SHA384 + """ + RSA_SHA384 + + """ + RSA-SHA512 + """ + RSA_SHA512 +} + +""" +A Saved Reply is text a user can use to reply quickly. +""" +type SavedReply implements Node { + """ + The body of the saved reply. + """ + body: String! + + """ + The saved reply body rendered to HTML. + """ + bodyHTML: HTML! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the SavedReply object + """ + id: ID! + + """ + The title of the saved reply. + """ + title: String! + + """ + The user that saved this reply. + """ + user: Actor +} + +""" +The connection type for SavedReply. +""" +type SavedReplyConnection { + """ + A list of edges. + """ + edges: [SavedReplyEdge] + + """ + A list of nodes. + """ + nodes: [SavedReply] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SavedReplyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SavedReply +} + +""" +Ordering options for saved reply connections. +""" +input SavedReplyOrder { + """ + The field to order saved replies by. + """ + field: SavedReplyOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which saved reply connections can be ordered. +""" +enum SavedReplyOrderField { + """ + Order saved reply by when they were updated. + """ + UPDATED_AT +} + +""" +The results of a search. +""" +union SearchResultItem = App|Discussion|Issue|MarketplaceListing|Organization|PullRequest|Repository|User + +""" +A list of results that matched against a search query. Regardless of the number of matches, a maximum of 1,000 results will be available across all types, potentially split across many pages. +""" +type SearchResultItemConnection { + """ + The total number of pieces of code that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + codeCount: Int! + + """ + The total number of discussions that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + discussionCount: Int! + + """ + A list of edges. + """ + edges: [SearchResultItemEdge] + + """ + The total number of issues that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + issueCount: Int! + + """ + A list of nodes. + """ + nodes: [SearchResultItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The total number of repositories that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + repositoryCount: Int! + + """ + The total number of users that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + userCount: Int! + + """ + The total number of wiki pages that matched the search query. Regardless of the total number of matches, a maximum of 1,000 results will be available across all types. + """ + wikiCount: Int! +} + +""" +An edge in a connection. +""" +type SearchResultItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SearchResultItem + + """ + Text matches on the result found. + """ + textMatches: [TextMatch] +} + +""" +Represents the individual results of a search. +""" +enum SearchType { + """ + Returns results matching issues in repositories. + """ + ISSUE + + """ + Returns results matching repositories. + """ + REPOSITORY + + """ + Returns results matching users and organizations on GitHub. + """ + USER + + """ + Returns matching discussions in repositories. + """ + DISCUSSION +} + +""" +A GitHub Security Advisory +""" +type SecurityAdvisory implements Node { + """ + The classification of the advisory + """ + classification: SecurityAdvisoryClassification! + + """ + The CVSS associated with this advisory + """ + cvss: CVSS! + + """ + CWEs associated with this Advisory + """ + cwes("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CWEConnection! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + This is a long plaintext description of the advisory + """ + description: String! + + """ + The GitHub Security Advisory ID + """ + ghsaId: String! + + """ + The Node ID of the SecurityAdvisory object + """ + id: ID! + + """ + A list of identifiers for this advisory + """ + identifiers: [SecurityAdvisoryIdentifier!]! + + """ + The permalink for the advisory's dependabot alerts page + """ + notificationsPermalink: URI + + """ + The organization that originated the advisory + """ + origin: String! + + """ + The permalink for the advisory + """ + permalink: URI + + """ + When the advisory was published + """ + publishedAt: DateTime! + + """ + A list of references for this advisory + """ + references: [SecurityAdvisoryReference!]! + + """ + The severity of the advisory + """ + severity: SecurityAdvisorySeverity! + + """ + A short plaintext summary of the advisory + """ + summary: String! + + """ + When the advisory was last updated + """ + updatedAt: DateTime! + + """ + Vulnerabilities associated with this Advisory + """ + vulnerabilities("Ordering options for the returned topics." orderBy: SecurityVulnerabilityOrder = { + field: UPDATED_AT + direction: DESC + } + , "An ecosystem to filter vulnerabilities by." ecosystem: SecurityAdvisoryEcosystem, "A package name to filter vulnerabilities by." package: String, "A list of severities to filter vulnerabilities by." severities: [SecurityAdvisorySeverity!], "A list of advisory classifications to filter vulnerabilities by." classifications: [SecurityAdvisoryClassification!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SecurityVulnerabilityConnection! + + """ + When the advisory was withdrawn, if it has been withdrawn + """ + withdrawnAt: DateTime +} + +""" +Classification of the advisory. +""" +enum SecurityAdvisoryClassification { + """ + Classification of general advisories. + """ + GENERAL + + """ + Classification of malware advisories. + """ + MALWARE +} + +""" +The connection type for SecurityAdvisory. +""" +type SecurityAdvisoryConnection { + """ + A list of edges. + """ + edges: [SecurityAdvisoryEdge] + + """ + A list of nodes. + """ + nodes: [SecurityAdvisory] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +The possible ecosystems of a security vulnerability's package. +""" +enum SecurityAdvisoryEcosystem { + """ + PHP packages hosted at packagist.org + """ + COMPOSER + + """ + Erlang/Elixir packages hosted at hex.pm + """ + ERLANG + + """ + GitHub Actions + """ + ACTIONS + + """ + Go modules + """ + GO + + """ + Java artifacts hosted at the Maven central repository + """ + MAVEN + + """ + JavaScript packages hosted at npmjs.com + """ + NPM + + """ + .NET packages hosted at the NuGet Gallery + """ + NUGET + + """ + Python packages hosted at PyPI.org + """ + PIP + + """ + Dart packages hosted at pub.dev + """ + PUB + + """ + Ruby gems hosted at RubyGems.org + """ + RUBYGEMS + + """ + Rust crates + """ + RUST + + """ + Swift packages + """ + SWIFT +} + +""" +An edge in a connection. +""" +type SecurityAdvisoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityAdvisory +} + +""" +A GitHub Security Advisory Identifier +""" +type SecurityAdvisoryIdentifier { + """ + The identifier type, e.g. GHSA, CVE + """ + type: String! + + """ + The identifier + """ + value: String! +} + +""" +An advisory identifier to filter results on. +""" +input SecurityAdvisoryIdentifierFilter { + """ + The identifier type. + """ + type: SecurityAdvisoryIdentifierType! + + """ + The identifier string. Supports exact or partial matching. + """ + value: String! +} + +""" +Identifier formats available for advisories. +""" +enum SecurityAdvisoryIdentifierType { + """ + Common Vulnerabilities and Exposures Identifier. + """ + CVE + + """ + GitHub Security Advisory ID. + """ + GHSA +} + +""" +Ordering options for security advisory connections +""" +input SecurityAdvisoryOrder { + """ + The field to order security advisories by. + """ + field: SecurityAdvisoryOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which security advisory connections can be ordered. +""" +enum SecurityAdvisoryOrderField { + """ + Order advisories by publication time + """ + PUBLISHED_AT + + """ + Order advisories by update time + """ + UPDATED_AT +} + +""" +An individual package +""" +type SecurityAdvisoryPackage { + """ + The ecosystem the package belongs to, e.g. RUBYGEMS, NPM + """ + ecosystem: SecurityAdvisoryEcosystem! + + """ + The package name + """ + name: String! +} + +""" +An individual package version +""" +type SecurityAdvisoryPackageVersion { + """ + The package name or version + """ + identifier: String! +} + +""" +A GitHub Security Advisory Reference +""" +type SecurityAdvisoryReference { + """ + A publicly accessible reference + """ + url: URI! +} + +""" +Severity of the vulnerability. +""" +enum SecurityAdvisorySeverity { + """ + Low. + """ + LOW + + """ + Moderate. + """ + MODERATE + + """ + High. + """ + HIGH + + """ + Critical. + """ + CRITICAL +} + +""" +An individual vulnerability within an Advisory +""" +type SecurityVulnerability { + """ + The Advisory associated with this Vulnerability + """ + advisory: SecurityAdvisory! + + """ + The first version containing a fix for the vulnerability + """ + firstPatchedVersion: SecurityAdvisoryPackageVersion + + """ + A description of the vulnerable package + """ + package: SecurityAdvisoryPackage! + + """ + The severity of the vulnerability within this package + """ + severity: SecurityAdvisorySeverity! + + """ + When the vulnerability was last updated + """ + updatedAt: DateTime! + + """ + A string that describes the vulnerable package versions. + This string follows a basic syntax with a few forms. + + `= 0.2.0` denotes a single vulnerable version. + + `<= 1.0.8` denotes a version range up to and including the specified version + + `< 0.1.11` denotes a version range up to, but excluding, the specified version + + `>= 4.3.0, < 4.3.5` denotes a version range with a known minimum and maximum version. + + `>= 0.0.1` denotes a version range with a known minimum, but no known maximum + """ + vulnerableVersionRange: String! +} + +""" +The connection type for SecurityVulnerability. +""" +type SecurityVulnerabilityConnection { + """ + A list of edges. + """ + edges: [SecurityVulnerabilityEdge] + + """ + A list of nodes. + """ + nodes: [SecurityVulnerability] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SecurityVulnerabilityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SecurityVulnerability +} + +""" +Ordering options for security vulnerability connections +""" +input SecurityVulnerabilityOrder { + """ + The field to order security vulnerabilities by. + """ + field: SecurityVulnerabilityOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which security vulnerability connections can be ordered. +""" +enum SecurityVulnerabilityOrderField { + """ + Order vulnerability by update time + """ + UPDATED_AT +} + +""" +Autogenerated input type of SetEnterpriseIdentityProvider +""" +input SetEnterpriseIdentityProviderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set an identity provider. + """ + enterpriseId: ID! + + """ + The URL endpoint for the identity provider's SAML SSO. + """ + ssoUrl: URI! + + """ + The Issuer Entity ID for the SAML identity provider + """ + issuer: String + + """ + The x509 certificate used by the identity provider to sign assertions and responses. + """ + idpCertificate: String! + + """ + The signature algorithm used to sign SAML requests for the identity provider. + """ + signatureMethod: SamlSignatureAlgorithm! + + """ + The digest algorithm used to sign SAML requests for the identity provider. + """ + digestMethod: SamlDigestAlgorithm! +} + +""" +Autogenerated return type of SetEnterpriseIdentityProvider. +""" +type SetEnterpriseIdentityProviderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The identity provider for the enterprise. + """ + identityProvider: EnterpriseIdentityProvider +} + +""" +Autogenerated input type of SetOrganizationInteractionLimit +""" +input SetOrganizationInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization to set a limit for. + """ + organizationId: ID! + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry +} + +""" +Autogenerated return type of SetOrganizationInteractionLimit. +""" +type SetOrganizationInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that the interaction limit was set for. + """ + organization: Organization +} + +""" +Autogenerated input type of SetRepositoryInteractionLimit +""" +input SetRepositoryInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to set a limit for. + """ + repositoryId: ID! + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry +} + +""" +Autogenerated return type of SetRepositoryInteractionLimit. +""" +type SetRepositoryInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that the interaction limit was set for. + """ + repository: Repository +} + +""" +Autogenerated input type of SetUserInteractionLimit +""" +input SetUserInteractionLimitInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user to set a limit for. + """ + userId: ID! + + """ + The limit to set. + """ + limit: RepositoryInteractionLimit! + + """ + When this limit should expire. + """ + expiry: RepositoryInteractionLimitExpiry +} + +""" +Autogenerated return type of SetUserInteractionLimit. +""" +type SetUserInteractionLimitPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that the interaction limit was set for. + """ + user: User +} + +""" +Represents an S/MIME signature on a Commit or Tag. +""" +type SmimeSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Social media profile associated with a user. +""" +type SocialAccount { + """ + Name of the social media account as it appears on the profile. + """ + displayName: String! + + """ + Software or company that hosts the social media account. + """ + provider: SocialAccountProvider! + + """ + URL of the social media account. + """ + url: URI! +} + +""" +The connection type for SocialAccount. +""" +type SocialAccountConnection { + """ + A list of edges. + """ + edges: [SocialAccountEdge] + + """ + A list of nodes. + """ + nodes: [SocialAccount] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SocialAccountEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SocialAccount +} + +""" +Software or company that hosts social media accounts. +""" +enum SocialAccountProvider { + """ + Catch-all for social media providers that do not yet have specific handling. + """ + GENERIC + + """ + Social media and networking website. + """ + FACEBOOK + + """ + Fork of Mastodon with a greater focus on local posting. + """ + HOMETOWN + + """ + Social media website with a focus on photo and video sharing. + """ + INSTAGRAM + + """ + Professional networking website. + """ + LINKEDIN + + """ + Open-source federated microblogging service. + """ + MASTODON + + """ + Social news aggregation and discussion website. + """ + REDDIT + + """ + Live-streaming service. + """ + TWITCH + + """ + Microblogging website. + """ + TWITTER + + """ + Online video platform. + """ + YOUTUBE + + """ + JavaScript package registry. + """ + NPM +} + +""" +Entities that can sponsor others via GitHub Sponsors +""" +union Sponsor = Organization|User + +""" +A GitHub account and the total amount in USD they've paid for sponsorships to a particular maintainer. Does not include payments made via Patreon. +""" +type SponsorAndLifetimeValue { + """ + The amount in cents. + """ + amountInCents: Int! + + """ + The amount in USD, formatted as a string. + """ + formattedAmount: String! + + """ + The sponsor's GitHub account. + """ + sponsor: Sponsorable! + + """ + The maintainer's GitHub account. + """ + sponsorable: Sponsorable! +} + +""" +The connection type for SponsorAndLifetimeValue. +""" +type SponsorAndLifetimeValueConnection { + """ + A list of edges. + """ + edges: [SponsorAndLifetimeValueEdge] + + """ + A list of nodes. + """ + nodes: [SponsorAndLifetimeValue] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorAndLifetimeValueEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorAndLifetimeValue +} + +""" +Ordering options for connections to get sponsor entities and associated USD amounts for GitHub Sponsors. +""" +input SponsorAndLifetimeValueOrder { + """ + The field to order results by. + """ + field: SponsorAndLifetimeValueOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which sponsor and lifetime value connections can be ordered. +""" +enum SponsorAndLifetimeValueOrderField { + """ + Order results by the sponsor's login (username). + """ + SPONSOR_LOGIN + + """ + Order results by the sponsor's relevance to the viewer. + """ + SPONSOR_RELEVANCE + + """ + Order results by how much money the sponsor has paid in total. + """ + LIFETIME_VALUE +} + +""" +A list of users and organizations sponsoring someone via GitHub Sponsors. +""" +type SponsorConnection { + """ + A list of edges. + """ + edges: [SponsorEdge] + + """ + A list of nodes. + """ + nodes: [Sponsor] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user or organization who is sponsoring someone in GitHub Sponsors. +""" +type SponsorEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Sponsor +} + +""" +Ordering options for connections to get sponsor entities for GitHub Sponsors. +""" +input SponsorOrder { + """ + The field to order sponsor entities by. + """ + field: SponsorOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which sponsor connections can be ordered. +""" +enum SponsorOrderField { + """ + Order sponsorable entities by login (username). + """ + LOGIN + + """ + Order sponsors by their relevance to the viewer. + """ + RELEVANCE +} + +""" +Entities that can sponsor or be sponsored through GitHub Sponsors. +""" +interface Sponsorable { + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + Whether the given account is sponsoring this user/organization. + """ + isSponsoredBy("The target account's login." accountLogin: String!): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Calculate how much each sponsor has ever paid total to this maintainer via GitHub Sponsors. Does not include sponsorships paid via Patreon. + """ + lifetimeReceivedSponsorshipValues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for results returned from the connection." orderBy: SponsorAndLifetimeValueOrder = { + field: SPONSOR_LOGIN + direction: ASC + } + ): SponsorAndLifetimeValueConnection! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the users and organizations returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If given, will filter for sponsors at the given tier. Will only return sponsors whose tier the viewer is permitted to see." tierId: ID, "Ordering options for sponsors returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter activities returned to only those that occurred in the most recent specified time period. Set to ALL to avoid filtering by when the activity occurred. Will be ignored if `since` or `until` is given." period: SponsorsActivityPeriod = MONTH, "Filter activities to those that occurred on or after this time." since: DateTime, "Filter activities to those that occurred before this time." until: DateTime, "Ordering options for activity returned from the connection." orderBy: SponsorsActivityOrder = { + field: TIMESTAMP + direction: DESC + } + , "Filter activities to only the specified actions." actions: [SponsorsActivityAction!] = [], "Whether to include those events where this sponsorable acted as the sponsor. Defaults to only including events where this sponsorable was the recipient of a sponsorship." includeAsSponsor: Boolean = false, "Whether or not to include private activities in the result set. Defaults to including public and private activities." includePrivate: Boolean = true): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the sponsorship where you're the sponsor. + """ + sponsorshipForViewerAsSponsor("Whether to return the sponsorship only if it's still active. Pass false to get the viewer's sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the sponsorship you're receiving. + """ + sponsorshipForViewerAsSponsorable("Whether to return the sponsorship only if it's still active. Pass false to get the sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorship updates returned from the connection." orderBy: SponsorshipNewsletterOrder = { + field: CREATED_AT + direction: DESC + } + ): SponsorshipNewsletterConnection! + + """ + The sponsorships where this user or organization is the maintainer receiving the funds. + """ + sponsorshipsAsMaintainer("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Whether or not to include private sponsorships in the result set" includePrivate: Boolean = false, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Whether to include only sponsorships that are active right now, versus all sponsorships this maintainer has ever received." activeOnly: Boolean = true): SponsorshipConnection! + + """ + The sponsorships where this user or organization is the funder. + """ + sponsorshipsAsSponsor("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Filter sponsorships returned to those for the specified maintainers. That is, the recipient of the sponsorship is a user or organization with one of the given logins." maintainerLogins: [String!], "Whether to include only sponsorships that are active right now, versus all sponsorships this sponsor has ever made." activeOnly: Boolean = true): SponsorshipConnection! + + """ + The amount in United States cents (e.g., 500 = $5.00 USD) that this entity has spent on GitHub to fund sponsorships. Only returns a value when viewed by the user themselves or by a user who can manage sponsorships for the requested organization. + """ + totalSponsorshipAmountAsSponsorInCents("Filter payments to those that occurred on or after this time." since: DateTime, "Filter payments to those that occurred before this time." until: DateTime, "Filter payments to those made to the users or organizations with the specified usernames." sponsorableLogins: [String!] = []): Int + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! +} + +""" +Entities that can be sponsored via GitHub Sponsors +""" +union SponsorableItem = Organization|User + +""" +The connection type for SponsorableItem. +""" +type SponsorableItemConnection { + """ + A list of edges. + """ + edges: [SponsorableItemEdge] + + """ + A list of nodes. + """ + nodes: [SponsorableItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorableItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorableItem +} + +""" +Ordering options for connections to get sponsorable entities for GitHub Sponsors. +""" +input SponsorableOrder { + """ + The field to order sponsorable entities by. + """ + field: SponsorableOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which sponsorable connections can be ordered. +""" +enum SponsorableOrderField { + """ + Order sponsorable entities by login (username). + """ + LOGIN +} + +""" +An event related to sponsorship activity. +""" +type SponsorsActivity implements Node { + """ + What action this activity indicates took place. + """ + action: SponsorsActivityAction! + + """ + The sponsor's current privacy level. + """ + currentPrivacyLevel: SponsorshipPrivacy + + """ + The Node ID of the SponsorsActivity object + """ + id: ID! + + """ + The platform that was used to pay for the sponsorship. + """ + paymentSource: SponsorshipPaymentSource + + """ + The tier that the sponsorship used to use, for tier change events. + """ + previousSponsorsTier: SponsorsTier + + """ + The user or organization who triggered this activity and was/is sponsoring the sponsorable. + """ + sponsor: Sponsor + + """ + The user or organization that is being sponsored, the maintainer. + """ + sponsorable: Sponsorable! + + """ + The associated sponsorship tier. + """ + sponsorsTier: SponsorsTier + + """ + The timestamp of this event. + """ + timestamp: DateTime + + """ + Was this sponsorship made alongside other sponsorships at the same time from the same sponsor? + """ + viaBulkSponsorship: Boolean! +} + +""" +The possible actions that GitHub Sponsors activities can represent. +""" +enum SponsorsActivityAction { + """ + The activity was starting a sponsorship. + """ + NEW_SPONSORSHIP + + """ + The activity was cancelling a sponsorship. + """ + CANCELLED_SPONSORSHIP + + """ + The activity was changing the sponsorship tier, either directly by the sponsor or by a scheduled/pending change. + """ + TIER_CHANGE + + """ + The activity was funds being refunded to the sponsor or GitHub. + """ + REFUND + + """ + The activity was scheduling a downgrade or cancellation. + """ + PENDING_CHANGE + + """ + The activity was disabling matching for a previously matched sponsorship. + """ + SPONSOR_MATCH_DISABLED +} + +""" +The connection type for SponsorsActivity. +""" +type SponsorsActivityConnection { + """ + A list of edges. + """ + edges: [SponsorsActivityEdge] + + """ + A list of nodes. + """ + nodes: [SponsorsActivity] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorsActivityEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorsActivity +} + +""" +Ordering options for GitHub Sponsors activity connections. +""" +input SponsorsActivityOrder { + """ + The field to order activity by. + """ + field: SponsorsActivityOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which GitHub Sponsors activity connections can be ordered. +""" +enum SponsorsActivityOrderField { + """ + Order activities by when they happened. + """ + TIMESTAMP +} + +""" +The possible time periods for which Sponsors activities can be requested. +""" +enum SponsorsActivityPeriod { + """ + The previous calendar day. + """ + DAY + + """ + The previous seven days. + """ + WEEK + + """ + The previous thirty days. + """ + MONTH + + """ + Don't restrict the activity to any date range, include all activity. + """ + ALL +} + +""" +Represents countries or regions for billing and residence for a GitHub Sponsors profile. +""" +enum SponsorsCountryOrRegionCode { + """ + Afghanistan + """ + AF + + """ + Åland + """ + AX + + """ + Albania + """ + AL + + """ + Algeria + """ + DZ + + """ + American Samoa + """ + AS + + """ + Andorra + """ + AD + + """ + Angola + """ + AO + + """ + Anguilla + """ + AI + + """ + Antarctica + """ + AQ + + """ + Antigua and Barbuda + """ + AG + + """ + Argentina + """ + AR + + """ + Armenia + """ + AM + + """ + Aruba + """ + AW + + """ + Australia + """ + AU + + """ + Austria + """ + AT + + """ + Azerbaijan + """ + AZ + + """ + Bahamas + """ + BS + + """ + Bahrain + """ + BH + + """ + Bangladesh + """ + BD + + """ + Barbados + """ + BB + + """ + Belarus + """ + BY + + """ + Belgium + """ + BE + + """ + Belize + """ + BZ + + """ + Benin + """ + BJ + + """ + Bermuda + """ + BM + + """ + Bhutan + """ + BT + + """ + Bolivia + """ + BO + + """ + Bonaire, Sint Eustatius and Saba + """ + BQ + + """ + Bosnia and Herzegovina + """ + BA + + """ + Botswana + """ + BW + + """ + Bouvet Island + """ + BV + + """ + Brazil + """ + BR + + """ + British Indian Ocean Territory + """ + IO + + """ + Brunei Darussalam + """ + BN + + """ + Bulgaria + """ + BG + + """ + Burkina Faso + """ + BF + + """ + Burundi + """ + BI + + """ + Cambodia + """ + KH + + """ + Cameroon + """ + CM + + """ + Canada + """ + CA + + """ + Cape Verde + """ + CV + + """ + Cayman Islands + """ + KY + + """ + Central African Republic + """ + CF + + """ + Chad + """ + TD + + """ + Chile + """ + CL + + """ + China + """ + CN + + """ + Christmas Island + """ + CX + + """ + Cocos (Keeling) Islands + """ + CC + + """ + Colombia + """ + CO + + """ + Comoros + """ + KM + + """ + Congo (Brazzaville) + """ + CG + + """ + Congo (Kinshasa) + """ + CD + + """ + Cook Islands + """ + CK + + """ + Costa Rica + """ + CR + + """ + Côte d'Ivoire + """ + CI + + """ + Croatia + """ + HR + + """ + Curaçao + """ + CW + + """ + Cyprus + """ + CY + + """ + Czech Republic + """ + CZ + + """ + Denmark + """ + DK + + """ + Djibouti + """ + DJ + + """ + Dominica + """ + DM + + """ + Dominican Republic + """ + DO + + """ + Ecuador + """ + EC + + """ + Egypt + """ + EG + + """ + El Salvador + """ + SV + + """ + Equatorial Guinea + """ + GQ + + """ + Eritrea + """ + ER + + """ + Estonia + """ + EE + + """ + Ethiopia + """ + ET + + """ + Falkland Islands + """ + FK + + """ + Faroe Islands + """ + FO + + """ + Fiji + """ + FJ + + """ + Finland + """ + FI + + """ + France + """ + FR + + """ + French Guiana + """ + GF + + """ + French Polynesia + """ + PF + + """ + French Southern Lands + """ + TF + + """ + Gabon + """ + GA + + """ + Gambia + """ + GM + + """ + Georgia + """ + GE + + """ + Germany + """ + DE + + """ + Ghana + """ + GH + + """ + Gibraltar + """ + GI + + """ + Greece + """ + GR + + """ + Greenland + """ + GL + + """ + Grenada + """ + GD + + """ + Guadeloupe + """ + GP + + """ + Guam + """ + GU + + """ + Guatemala + """ + GT + + """ + Guernsey + """ + GG + + """ + Guinea + """ + GN + + """ + Guinea-Bissau + """ + GW + + """ + Guyana + """ + GY + + """ + Haiti + """ + HT + + """ + Heard and McDonald Islands + """ + HM + + """ + Honduras + """ + HN + + """ + Hong Kong + """ + HK + + """ + Hungary + """ + HU + + """ + Iceland + """ + IS + + """ + India + """ + IN + + """ + Indonesia + """ + ID + + """ + Iran + """ + IR + + """ + Iraq + """ + IQ + + """ + Ireland + """ + IE + + """ + Isle of Man + """ + IM + + """ + Israel + """ + IL + + """ + Italy + """ + IT + + """ + Jamaica + """ + JM + + """ + Japan + """ + JP + + """ + Jersey + """ + JE + + """ + Jordan + """ + JO + + """ + Kazakhstan + """ + KZ + + """ + Kenya + """ + KE + + """ + Kiribati + """ + KI + + """ + Korea, South + """ + KR + + """ + Kuwait + """ + KW + + """ + Kyrgyzstan + """ + KG + + """ + Laos + """ + LA + + """ + Latvia + """ + LV + + """ + Lebanon + """ + LB + + """ + Lesotho + """ + LS + + """ + Liberia + """ + LR + + """ + Libya + """ + LY + + """ + Liechtenstein + """ + LI + + """ + Lithuania + """ + LT + + """ + Luxembourg + """ + LU + + """ + Macau + """ + MO + + """ + Macedonia + """ + MK + + """ + Madagascar + """ + MG + + """ + Malawi + """ + MW + + """ + Malaysia + """ + MY + + """ + Maldives + """ + MV + + """ + Mali + """ + ML + + """ + Malta + """ + MT + + """ + Marshall Islands + """ + MH + + """ + Martinique + """ + MQ + + """ + Mauritania + """ + MR + + """ + Mauritius + """ + MU + + """ + Mayotte + """ + YT + + """ + Mexico + """ + MX + + """ + Micronesia + """ + FM + + """ + Moldova + """ + MD + + """ + Monaco + """ + MC + + """ + Mongolia + """ + MN + + """ + Montenegro + """ + ME + + """ + Montserrat + """ + MS + + """ + Morocco + """ + MA + + """ + Mozambique + """ + MZ + + """ + Myanmar + """ + MM + + """ + Namibia + """ + NA + + """ + Nauru + """ + NR + + """ + Nepal + """ + NP + + """ + Netherlands + """ + NL + + """ + New Caledonia + """ + NC + + """ + New Zealand + """ + NZ + + """ + Nicaragua + """ + NI + + """ + Niger + """ + NE + + """ + Nigeria + """ + NG + + """ + Niue + """ + NU + + """ + Norfolk Island + """ + NF + + """ + Northern Mariana Islands + """ + MP + + """ + Norway + """ + NO + + """ + Oman + """ + OM + + """ + Pakistan + """ + PK + + """ + Palau + """ + PW + + """ + Palestine + """ + PS + + """ + Panama + """ + PA + + """ + Papua New Guinea + """ + PG + + """ + Paraguay + """ + PY + + """ + Peru + """ + PE + + """ + Philippines + """ + PH + + """ + Pitcairn + """ + PN + + """ + Poland + """ + PL + + """ + Portugal + """ + PT + + """ + Puerto Rico + """ + PR + + """ + Qatar + """ + QA + + """ + Reunion + """ + RE + + """ + Romania + """ + RO + + """ + Russian Federation + """ + RU + + """ + Rwanda + """ + RW + + """ + Saint Barthélemy + """ + BL + + """ + Saint Helena + """ + SH + + """ + Saint Kitts and Nevis + """ + KN + + """ + Saint Lucia + """ + LC + + """ + Saint Martin (French part) + """ + MF + + """ + Saint Pierre and Miquelon + """ + PM + + """ + Saint Vincent and the Grenadines + """ + VC + + """ + Samoa + """ + WS + + """ + San Marino + """ + SM + + """ + Sao Tome and Principe + """ + ST + + """ + Saudi Arabia + """ + SA + + """ + Senegal + """ + SN + + """ + Serbia + """ + RS + + """ + Seychelles + """ + SC + + """ + Sierra Leone + """ + SL + + """ + Singapore + """ + SG + + """ + Sint Maarten (Dutch part) + """ + SX + + """ + Slovakia + """ + SK + + """ + Slovenia + """ + SI + + """ + Solomon Islands + """ + SB + + """ + Somalia + """ + SO + + """ + South Africa + """ + ZA + + """ + South Georgia and South Sandwich Islands + """ + GS + + """ + South Sudan + """ + SS + + """ + Spain + """ + ES + + """ + Sri Lanka + """ + LK + + """ + Sudan + """ + SD + + """ + Suriname + """ + SR + + """ + Svalbard and Jan Mayen Islands + """ + SJ + + """ + Swaziland + """ + SZ + + """ + Sweden + """ + SE + + """ + Switzerland + """ + CH + + """ + Taiwan + """ + TW + + """ + Tajikistan + """ + TJ + + """ + Tanzania + """ + TZ + + """ + Thailand + """ + TH + + """ + Timor-Leste + """ + TL + + """ + Togo + """ + TG + + """ + Tokelau + """ + TK + + """ + Tonga + """ + TO + + """ + Trinidad and Tobago + """ + TT + + """ + Tunisia + """ + TN + + """ + Türkiye + """ + TR + + """ + Turkmenistan + """ + TM + + """ + Turks and Caicos Islands + """ + TC + + """ + Tuvalu + """ + TV + + """ + Uganda + """ + UG + + """ + Ukraine + """ + UA + + """ + United Arab Emirates + """ + AE + + """ + United Kingdom + """ + GB + + """ + United States Minor Outlying Islands + """ + UM + + """ + United States of America + """ + US + + """ + Uruguay + """ + UY + + """ + Uzbekistan + """ + UZ + + """ + Vanuatu + """ + VU + + """ + Vatican City + """ + VA + + """ + Venezuela + """ + VE + + """ + Vietnam + """ + VN + + """ + Virgin Islands, British + """ + VG + + """ + Virgin Islands, U.S. + """ + VI + + """ + Wallis and Futuna Islands + """ + WF + + """ + Western Sahara + """ + EH + + """ + Yemen + """ + YE + + """ + Zambia + """ + ZM + + """ + Zimbabwe + """ + ZW +} + +""" +A goal associated with a GitHub Sponsors listing, representing a target the sponsored maintainer would like to attain. +""" +type SponsorsGoal { + """ + A description of the goal from the maintainer. + """ + description: String + + """ + What the objective of this goal is. + """ + kind: SponsorsGoalKind! + + """ + The percentage representing how complete this goal is, between 0-100. + """ + percentComplete: Int! + + """ + What the goal amount is. Represents an amount in USD for monthly sponsorship amount goals. Represents a count of unique sponsors for total sponsors count goals. + """ + targetValue: Int! + + """ + A brief summary of the kind and target value of this goal. + """ + title: String! +} + +""" +The different kinds of goals a GitHub Sponsors member can have. +""" +enum SponsorsGoalKind { + """ + The goal is about reaching a certain number of sponsors. + """ + TOTAL_SPONSORS_COUNT + + """ + The goal is about getting a certain amount in USD from sponsorships each month. + """ + MONTHLY_SPONSORSHIP_AMOUNT +} + +""" +A GitHub Sponsors listing. +""" +type SponsorsListing implements Node { + """ + The current goal the maintainer is trying to reach with GitHub Sponsors, if any. + """ + activeGoal: SponsorsGoal + + """ + The Stripe Connect account currently in use for payouts for this Sponsors listing, if any. Will only return a value when queried by the maintainer themselves, or by an admin of the sponsorable organization. + """ + activeStripeConnectAccount: StripeConnectAccount + + """ + The name of the country or region with the maintainer's bank account or fiscal host. Will only return a value when queried by the maintainer themselves, or by an admin of the sponsorable organization. + """ + billingCountryOrRegion: String + + """ + The email address used by GitHub to contact the sponsorable about their GitHub Sponsors profile. Will only return a value when queried by the maintainer themselves, or by an admin of the sponsorable organization. + """ + contactEmailAddress: String + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The HTTP path for the Sponsors dashboard for this Sponsors listing. + """ + dashboardResourcePath: URI! + + """ + The HTTP URL for the Sponsors dashboard for this Sponsors listing. + """ + dashboardUrl: URI! + + """ + The records featured on the GitHub Sponsors profile. + """ + featuredItems("The types of featured items to return." featureableTypes: [SponsorsListingFeaturedItemFeatureableType!] = [REPOSITORY,USER]): [SponsorsListingFeaturedItem!]! + + """ + The fiscal host used for payments, if any. Will only return a value when queried by the maintainer themselves, or by an admin of the sponsorable organization. + """ + fiscalHost: Organization + + """ + The full description of the listing. + """ + fullDescription: String! + + """ + The full description of the listing rendered to HTML. + """ + fullDescriptionHTML: HTML! + + """ + The Node ID of the SponsorsListing object + """ + id: ID! + + """ + Whether this listing is publicly visible. + """ + isPublic: Boolean! + + """ + The listing's full name. + """ + name: String! + + """ + A future date on which this listing is eligible to receive a payout. + """ + nextPayoutDate: Date + + """ + The name of the country or region where the maintainer resides. Will only return a value when queried by the maintainer themselves, or by an admin of the sponsorable organization. + """ + residenceCountryOrRegion: String + + """ + The HTTP path for this Sponsors listing. + """ + resourcePath: URI! + + """ + The short description of the listing. + """ + shortDescription: String! + + """ + The short name of the listing. + """ + slug: String! + + """ + The entity this listing represents who can be sponsored on GitHub Sponsors. + """ + sponsorable: Sponsorable! + + """ + The tiers for this GitHub Sponsors profile. + """ + tiers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for Sponsors tiers returned from the connection." orderBy: SponsorsTierOrder = { + field: MONTHLY_PRICE_IN_CENTS + direction: ASC + } + , "Whether to include tiers that aren't published. Only admins of the Sponsors listing can see draft tiers. Only admins of the Sponsors listing and viewers who are currently sponsoring on a retired tier can see those retired tiers. Defaults to including only published tiers, which are visible to anyone who can see the GitHub Sponsors profile." includeUnpublished: Boolean = false): SponsorsTierConnection + + """ + The HTTP URL for this Sponsors listing. + """ + url: URI! +} + +""" +A record that can be featured on a GitHub Sponsors profile. +""" +union SponsorsListingFeatureableItem = Repository|User + +""" +A record that is promoted on a GitHub Sponsors profile. +""" +type SponsorsListingFeaturedItem implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Will either be a description from the sponsorable maintainer about why they featured this item, or the item's description itself, such as a user's bio from their GitHub profile page. + """ + description: String + + """ + The record that is featured on the GitHub Sponsors profile. + """ + featureable: SponsorsListingFeatureableItem! + + """ + The Node ID of the SponsorsListingFeaturedItem object + """ + id: ID! + + """ + The position of this featured item on the GitHub Sponsors profile with a lower position indicating higher precedence. Starts at 1. + """ + position: Int! + + """ + The GitHub Sponsors profile that features this record. + """ + sponsorsListing: SponsorsListing! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The different kinds of records that can be featured on a GitHub Sponsors profile page. +""" +enum SponsorsListingFeaturedItemFeatureableType { + """ + A repository owned by the user or organization with the GitHub Sponsors profile. + """ + REPOSITORY + + """ + A user who belongs to the organization with the GitHub Sponsors profile. + """ + USER +} + +""" +A GitHub Sponsors tier associated with a GitHub Sponsors listing. +""" +type SponsorsTier implements Node { + """ + SponsorsTier information only visible to users that can administer the associated Sponsors listing. + """ + adminInfo: SponsorsTierAdminInfo + + """ + Get a different tier for this tier's maintainer that is at the same frequency as this tier but with an equal or lesser cost. Returns the published tier with the monthly price closest to this tier's without going over. + """ + closestLesserValueTier: SponsorsTier + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of the tier. + """ + description: String! + + """ + The tier description rendered to HTML + """ + descriptionHTML: HTML! + + """ + The Node ID of the SponsorsTier object + """ + id: ID! + + """ + Whether this tier was chosen at checkout time by the sponsor rather than defined ahead of time by the maintainer who manages the Sponsors listing. + """ + isCustomAmount: Boolean! + + """ + Whether this tier is only for use with one-time sponsorships. + """ + isOneTime: Boolean! + + """ + How much this tier costs per month in cents. + """ + monthlyPriceInCents: Int! + + """ + How much this tier costs per month in USD. + """ + monthlyPriceInDollars: Int! + + """ + The name of the tier. + """ + name: String! + + """ + The sponsors listing that this tier belongs to. + """ + sponsorsListing: SponsorsListing! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +SponsorsTier information only visible to users that can administer the associated Sponsors listing. +""" +type SponsorsTierAdminInfo { + """ + Indicates whether this tier is still a work in progress by the sponsorable and not yet published to the associated GitHub Sponsors profile. Draft tiers cannot be used for new sponsorships and will not be in use on existing sponsorships. Draft tiers cannot be seen by anyone but the admins of the GitHub Sponsors profile. + """ + isDraft: Boolean! + + """ + Indicates whether this tier is published to the associated GitHub Sponsors profile. Published tiers are visible to anyone who can see the GitHub Sponsors profile, and are available for use in sponsorships if the GitHub Sponsors profile is publicly visible. + """ + isPublished: Boolean! + + """ + Indicates whether this tier has been retired from the associated GitHub Sponsors profile. Retired tiers are no longer shown on the GitHub Sponsors profile and cannot be chosen for new sponsorships. Existing sponsorships may still use retired tiers if the sponsor selected the tier before it was retired. + """ + isRetired: Boolean! + + """ + The sponsorships using this tier. + """ + sponsorships("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Whether or not to return private sponsorships using this tier. Defaults to only returning public sponsorships on this tier." includePrivate: Boolean = false, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder): SponsorshipConnection! +} + +""" +The connection type for SponsorsTier. +""" +type SponsorsTierConnection { + """ + A list of edges. + """ + edges: [SponsorsTierEdge] + + """ + A list of nodes. + """ + nodes: [SponsorsTier] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorsTierEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorsTier +} + +""" +Ordering options for Sponsors tiers connections. +""" +input SponsorsTierOrder { + """ + The field to order tiers by. + """ + field: SponsorsTierOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which Sponsors tiers connections can be ordered. +""" +enum SponsorsTierOrderField { + """ + Order tiers by creation time. + """ + CREATED_AT + + """ + Order tiers by their monthly price in cents + """ + MONTHLY_PRICE_IN_CENTS +} + +""" +A sponsorship relationship between a sponsor and a maintainer +""" +type Sponsorship implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the Sponsorship object + """ + id: ID! + + """ + Whether the sponsorship is active. False implies the sponsor is a past sponsor of the maintainer, while true implies they are a current sponsor. + """ + isActive: Boolean! + + """ + Whether this sponsorship represents a one-time payment versus a recurring sponsorship. + """ + isOneTimePayment: Boolean! + + """ + Whether the sponsor has chosen to receive sponsorship update emails sent from the sponsorable. Only returns a non-null value when the viewer has permission to know this. + """ + isSponsorOptedIntoEmail: Boolean + + """ + The entity that is being sponsored + """ + maintainer: User! @deprecated(reason: "`Sponsorship.maintainer` will be removed. Use `Sponsorship.sponsorable` instead. Removal on 2020-04-01 UTC.") + + """ + The platform that was most recently used to pay for the sponsorship. + """ + paymentSource: SponsorshipPaymentSource + + """ + The privacy level for this sponsorship. + """ + privacyLevel: SponsorshipPrivacy! + + """ + The user that is sponsoring. Returns null if the sponsorship is private or if sponsor is not a user. + """ + sponsor: User @deprecated(reason: "`Sponsorship.sponsor` will be removed. Use `Sponsorship.sponsorEntity` instead. Removal on 2020-10-01 UTC.") + + """ + The user or organization that is sponsoring, if you have permission to view them. + """ + sponsorEntity: Sponsor + + """ + The entity that is being sponsored + """ + sponsorable: Sponsorable! + + """ + The associated sponsorship tier + """ + tier: SponsorsTier + + """ + Identifies the date and time when the current tier was chosen for this sponsorship. + """ + tierSelectedAt: DateTime +} + +""" +A list of sponsorships either from the subject or received by the subject. +""" +type SponsorshipConnection { + """ + A list of edges. + """ + edges: [SponsorshipEdge] + + """ + A list of nodes. + """ + nodes: [Sponsorship] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! + + """ + The total amount in cents of all recurring sponsorships in the connection whose amount you can view. Does not include one-time sponsorships. + """ + totalRecurringMonthlyPriceInCents: Int! + + """ + The total amount in USD of all recurring sponsorships in the connection whose amount you can view. Does not include one-time sponsorships. + """ + totalRecurringMonthlyPriceInDollars: Int! +} + +""" +An edge in a connection. +""" +type SponsorshipEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Sponsorship +} + +""" +An update sent to sponsors of a user or organization on GitHub Sponsors. +""" +type SponsorshipNewsletter implements Node { + """ + The author of the newsletter. + """ + author: User + + """ + The contents of the newsletter, the message the sponsorable wanted to give. + """ + body: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the SponsorshipNewsletter object + """ + id: ID! + + """ + Indicates if the newsletter has been made available to sponsors. + """ + isPublished: Boolean! + + """ + The user or organization this newsletter is from. + """ + sponsorable: Sponsorable! + + """ + The subject of the newsletter, what it's about. + """ + subject: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +The connection type for SponsorshipNewsletter. +""" +type SponsorshipNewsletterConnection { + """ + A list of edges. + """ + edges: [SponsorshipNewsletterEdge] + + """ + A list of nodes. + """ + nodes: [SponsorshipNewsletter] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SponsorshipNewsletterEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: SponsorshipNewsletter +} + +""" +Ordering options for sponsorship newsletter connections. +""" +input SponsorshipNewsletterOrder { + """ + The field to order sponsorship newsletters by. + """ + field: SponsorshipNewsletterOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which sponsorship update connections can be ordered. +""" +enum SponsorshipNewsletterOrderField { + """ + Order sponsorship newsletters by when they were created. + """ + CREATED_AT +} + +""" +Ordering options for sponsorship connections. +""" +input SponsorshipOrder { + """ + The field to order sponsorship by. + """ + field: SponsorshipOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which sponsorship connections can be ordered. +""" +enum SponsorshipOrderField { + """ + Order sponsorship by creation time. + """ + CREATED_AT +} + +""" +How payment was made for funding a GitHub Sponsors sponsorship. +""" +enum SponsorshipPaymentSource { + """ + Payment was made through GitHub. + """ + GITHUB + + """ + Payment was made through Patreon. + """ + PATREON +} + +""" +The privacy of a sponsorship +""" +enum SponsorshipPrivacy { + """ + Public + """ + PUBLIC + + """ + Private + """ + PRIVATE +} + +""" +The possible default commit messages for squash merges. +""" +enum SquashMergeCommitMessage { + """ + Default to the pull request's body. + """ + PR_BODY + + """ + Default to the branch's commit messages. + """ + COMMIT_MESSAGES + + """ + Default to a blank commit message. + """ + BLANK +} + +""" +The possible default commit titles for squash merges. +""" +enum SquashMergeCommitTitle { + """ + Default to the pull request's title. + """ + PR_TITLE + + """ + Default to the commit's title (if only one commit) or the pull request's title (when more than one commit). + """ + COMMIT_OR_PR_TITLE +} + +""" +Represents an SSH signature on a Commit or Tag. +""" +type SshSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Hex-encoded fingerprint of the key that signed this object. + """ + keyFingerprint: String + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Ways in which star connections can be ordered. +""" +input StarOrder { + """ + The field in which to order nodes by. + """ + field: StarOrderField! + + """ + The direction in which to order nodes. + """ + direction: OrderDirection! +} + +""" +Properties by which star connections can be ordered. +""" +enum StarOrderField { + """ + Allows ordering a list of stars by when they were created. + """ + STARRED_AT +} + +""" +The connection type for User. +""" +type StargazerConnection { + """ + A list of edges. + """ + edges: [StargazerEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user that's starred a repository. +""" +type StargazerEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + node: User! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Things that can be starred. +""" +interface Starrable { + """ + The Node ID of the Starrable object + """ + id: ID! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: StarOrder): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +The connection type for Repository. +""" +type StarredRepositoryConnection { + """ + A list of edges. + """ + edges: [StarredRepositoryEdge] + + """ + Is the list of stars for this user truncated? This is true for users that have many stars. + """ + isOverLimit: Boolean! + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a starred repository. +""" +type StarredRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + node: Repository! + + """ + Identifies when the item was starred. + """ + starredAt: DateTime! +} + +""" +Autogenerated input type of StartOrganizationMigration +""" +input StartOrganizationMigrationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The URL of the organization to migrate. + """ + sourceOrgUrl: URI! + + """ + The name of the target organization. + """ + targetOrgName: String! + + """ + The ID of the enterprise the target organization belongs to. + """ + targetEnterpriseId: ID! + + """ + The migration source access token. + """ + sourceAccessToken: String! +} + +""" +Autogenerated return type of StartOrganizationMigration. +""" +type StartOrganizationMigrationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new organization migration. + """ + orgMigration: OrganizationMigration +} + +""" +Autogenerated input type of StartRepositoryMigration +""" +input StartRepositoryMigrationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the migration source. + """ + sourceId: ID! + + """ + The ID of the organization that will own the imported repository. + """ + ownerId: ID! + + """ + The URL of the source repository. + """ + sourceRepositoryUrl: URI! + + """ + The name of the imported repository. + """ + repositoryName: String! + + """ + Whether to continue the migration on error. Defaults to `true`. + """ + continueOnError: Boolean + + """ + The signed URL to access the user-uploaded git archive. + """ + gitArchiveUrl: String + + """ + The signed URL to access the user-uploaded metadata archive. + """ + metadataArchiveUrl: String + + """ + The migration source access token. + """ + accessToken: String + + """ + The GitHub personal access token of the user importing to the target repository. + """ + githubPat: String + + """ + Whether to skip migrating releases for the repository. + """ + skipReleases: Boolean + + """ + The visibility of the imported repository. + """ + targetRepoVisibility: String + + """ + Whether to lock the source repository. + """ + lockSource: Boolean +} + +""" +Autogenerated return type of StartRepositoryMigration. +""" +type StartRepositoryMigrationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The new repository migration. + """ + repositoryMigration: RepositoryMigration +} + +""" +Represents a commit status. +""" +type Status implements Node { + """ + A list of status contexts and check runs for this commit. + """ + combinedContexts("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): StatusCheckRollupContextConnection! + + """ + The commit this status is attached to. + """ + commit: Commit + + """ + Looks up an individual status context by context name. + """ + context("The context name." name: String!): StatusContext + + """ + The individual status contexts for this commit. + """ + contexts: [StatusContext!]! + + """ + The Node ID of the Status object + """ + id: ID! + + """ + The combined commit status. + """ + state: StatusState! +} + +""" +Required status check +""" +type StatusCheckConfiguration { + """ + The status check context name that must be present on the commit. + """ + context: String! + + """ + The optional integration ID that this status check must originate from. + """ + integrationId: Int +} + +""" +Required status check +""" +input StatusCheckConfigurationInput { + """ + The status check context name that must be present on the commit. + """ + context: String! + + """ + The optional integration ID that this status check must originate from. + """ + integrationId: Int +} + +""" +Represents the rollup for both the check runs and status for a commit. +""" +type StatusCheckRollup implements Node { + """ + The commit the status and check runs are attached to. + """ + commit: Commit + + """ + A list of status contexts and check runs for this commit. + """ + contexts("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): StatusCheckRollupContextConnection! + + """ + The Node ID of the StatusCheckRollup object + """ + id: ID! + + """ + The combined status for the commit. + """ + state: StatusState! +} + +""" +Types that can be inside a StatusCheckRollup context. +""" +union StatusCheckRollupContext = CheckRun|StatusContext + +""" +The connection type for StatusCheckRollupContext. +""" +type StatusCheckRollupContextConnection { + """ + The number of check runs in this rollup. + """ + checkRunCount: Int! + + """ + Counts of check runs by state. + """ + checkRunCountsByState: [CheckRunStateCount!] + + """ + A list of edges. + """ + edges: [StatusCheckRollupContextEdge] + + """ + A list of nodes. + """ + nodes: [StatusCheckRollupContext] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The number of status contexts in this rollup. + """ + statusContextCount: Int! + + """ + Counts of status contexts by state. + """ + statusContextCountsByState: [StatusContextStateCount!] + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type StatusCheckRollupContextEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: StatusCheckRollupContext +} + +""" +Represents an individual commit status context +""" +type StatusContext implements Node & RequirableByPullRequest { + """ + The avatar of the OAuth application or the user that created the status + """ + avatarUrl("The size of the resulting square image." size: Int = 40): URI + + """ + This commit this status context is attached to. + """ + commit: Commit + + """ + The name of this status context. + """ + context: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The actor who created this status context. + """ + creator: Actor + + """ + The description for this status context. + """ + description: String + + """ + The Node ID of the StatusContext object + """ + id: ID! + + """ + Whether this is required to pass before merging for a specific pull request. + """ + isRequired("The id of the pull request this is required for" pullRequestId: ID, "The number of the pull request this is required for" pullRequestNumber: Int): Boolean! + + """ + The state of this status context. + """ + state: StatusState! + + """ + The URL for this status context. + """ + targetUrl: URI +} + +""" +Represents a count of the state of a status context. +""" +type StatusContextStateCount { + """ + The number of statuses with this state. + """ + count: Int! + + """ + The state of a status context. + """ + state: StatusState! +} + +""" +The possible commit status states. +""" +enum StatusState { + """ + Status is expected. + """ + EXPECTED + + """ + Status is errored. + """ + ERROR + + """ + Status is failing. + """ + FAILURE + + """ + Status is pending. + """ + PENDING + + """ + Status is successful. + """ + SUCCESS +} + +""" +A Stripe Connect account for receiving sponsorship funds from GitHub Sponsors. +""" +type StripeConnectAccount { + """ + The account number used to identify this Stripe Connect account. + """ + accountId: String! + + """ + The name of the country or region of an external account, such as a bank account, tied to the Stripe Connect account. Will only return a value when queried by the maintainer of the associated GitHub Sponsors profile themselves, or by an admin of the sponsorable organization. + """ + billingCountryOrRegion: String + + """ + The name of the country or region of the Stripe Connect account. Will only return a value when queried by the maintainer of the associated GitHub Sponsors profile themselves, or by an admin of the sponsorable organization. + """ + countryOrRegion: String + + """ + Whether this Stripe Connect account is currently in use for the associated GitHub Sponsors profile. + """ + isActive: Boolean! + + """ + The GitHub Sponsors profile associated with this Stripe Connect account. + """ + sponsorsListing: SponsorsListing! + + """ + The URL to access this Stripe Connect account on Stripe's website. + """ + stripeDashboardUrl: URI! +} + +""" +Autogenerated input type of SubmitPullRequestReview +""" +input SubmitPullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Pull Request ID to submit any pending reviews. + """ + pullRequestId: ID + + """ + The Pull Request Review ID to submit. + """ + pullRequestReviewId: ID + + """ + The event to send to the Pull Request Review. + """ + event: PullRequestReviewEvent! + + """ + The text field to set on the Pull Request Review. + """ + body: String +} + +""" +Autogenerated return type of SubmitPullRequestReview. +""" +type SubmitPullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The submitted pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +A pointer to a repository at a specific revision embedded inside another repository. +""" +type Submodule { + """ + The branch of the upstream submodule for tracking updates + """ + branch: String + + """ + The git URL of the submodule repository + """ + gitUrl: URI! + + """ + The name of the submodule in .gitmodules + """ + name: String! + + """ + The name of the submodule in .gitmodules (Base64-encoded) + """ + nameRaw: Base64String! + + """ + The path in the superproject that this submodule is located in + """ + path: String! + + """ + The path in the superproject that this submodule is located in (Base64-encoded) + """ + pathRaw: Base64String! + + """ + The commit revision of the subproject repository being tracked by the submodule + """ + subprojectCommitOid: GitObjectID +} + +""" +The connection type for Submodule. +""" +type SubmoduleConnection { + """ + A list of edges. + """ + edges: [SubmoduleEdge] + + """ + A list of nodes. + """ + nodes: [Submodule] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type SubmoduleEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Submodule +} + +""" +Entities that can be subscribed to for web and email notifications. +""" +interface Subscribable { + """ + The Node ID of the Subscribable object + """ + id: ID! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Entities that can be subscribed to for web and email notifications. +""" +interface SubscribableThread { + """ + The Node ID of the SubscribableThread object + """ + id: ID! + + """ + Identifies the viewer's thread subscription form action. + """ + viewerThreadSubscriptionFormAction: ThreadSubscriptionFormAction + + """ + Identifies the viewer's thread subscription status. + """ + viewerThreadSubscriptionStatus: ThreadSubscriptionState +} + +""" +Represents a 'subscribed' event on a given `Subscribable`. +""" +type SubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the SubscribedEvent object + """ + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +The possible states of a subscription. +""" +enum SubscriptionState { + """ + The User is only notified when participating or @mentioned. + """ + UNSUBSCRIBED + + """ + The User is notified of all conversations. + """ + SUBSCRIBED + + """ + The User is never notified. + """ + IGNORED +} + +""" +A suggestion to review a pull request based on a user's commit history and review comments. +""" +type SuggestedReviewer { + """ + Is this suggestion based on past commits? + """ + isAuthor: Boolean! + + """ + Is this suggestion based on past review comments? + """ + isCommenter: Boolean! + + """ + Identifies the user suggested to review the pull request. + """ + reviewer: User! +} + +""" +Represents a Git tag. +""" +type Tag implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + The Node ID of the Tag object + """ + id: ID! + + """ + The Git tag message. + """ + message: String + + """ + The Git tag name. + """ + name: String! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! + + """ + Details about the tag author. + """ + tagger: GitActor + + """ + The Git object the tag points to. + """ + target: GitObject! +} + +""" +Parameters to be used for the tag_name_pattern rule +""" +type TagNamePatternParameters { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean! + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +Parameters to be used for the tag_name_pattern rule +""" +input TagNamePatternParametersInput { + """ + How this rule will appear to users. + """ + name: String + + """ + If true, the rule will fail if the pattern matches. + """ + negate: Boolean + + """ + The operator to use for matching. + """ + operator: String! + + """ + The pattern to match with. + """ + pattern: String! +} + +""" +A team of users in an organization. +""" +type Team implements MemberStatusable & Node & Subscribable { + """ + A list of teams that are ancestors of this team. + """ + ancestors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): TeamConnection! + + """ + A URL pointing to the team's avatar. + """ + avatarUrl("The size in pixels of the resulting square image." size: Int = 400): URI + + """ + List of child teams belonging to this team + """ + childTeams("Order for connection" orderBy: TeamOrder, "User logins to filter by" userLogins: [String!], "Whether to list immediate child teams or all descendant child teams." immediateOnly: Boolean = true, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): TeamConnection! + + """ + The slug corresponding to the organization and team. + """ + combinedSlug: String! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the team. + """ + description: String + + """ + Find a team discussion by its number. + """ + discussion("The sequence number of the discussion to find." number: Int!): TeamDiscussion + + """ + A list of team discussions. + """ + discussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If provided, filters discussions according to whether or not they are pinned." isPinned: Boolean, "Order for connection" orderBy: TeamDiscussionOrder): TeamDiscussionConnection! + + """ + The HTTP path for team discussions + """ + discussionsResourcePath: URI! + + """ + The HTTP URL for team discussions + """ + discussionsUrl: URI! + + """ + The HTTP path for editing this team + """ + editTeamResourcePath: URI! + + """ + The HTTP URL for editing this team + """ + editTeamUrl: URI! + + """ + The Node ID of the Team object + """ + id: ID! + + """ + A list of pending invitations for users to this team + """ + invitations("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationInvitationConnection + + """ + Get the status messages members of this entity have set that are either public or visible only to the organization. + """ + memberStatuses("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for user statuses returned from the connection." orderBy: UserStatusOrder = { + field: UPDATED_AT + direction: DESC + } + ): UserStatusConnection! + + """ + A list of users who are members of this team. + """ + members("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The search string to look for." query: String, "Filter by membership type" membership: TeamMembershipType = ALL, "Filter by team member role" role: TeamMemberRole, "Order for the connection." orderBy: TeamMemberOrder): TeamMemberConnection! + + """ + The HTTP path for the team' members + """ + membersResourcePath: URI! + + """ + The HTTP URL for the team' members + """ + membersUrl: URI! + + """ + The name of the team. + """ + name: String! + + """ + The HTTP path creating a new team + """ + newTeamResourcePath: URI! + + """ + The HTTP URL creating a new team + """ + newTeamUrl: URI! + + """ + The notification setting that the team has set. + """ + notificationSetting: TeamNotificationSetting! + + """ + The organization that owns this team. + """ + organization: Organization! + + """ + The parent team of the team. + """ + parentTeam: Team + + """ + The level of privacy the team has. + """ + privacy: TeamPrivacy! + + """ + Finds and returns the project according to the provided project number. + """ + projectV2("The Project number." number: Int!): ProjectV2 + + """ + List of projects this team has collaborator access to. + """ + projectsV2("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Filtering options for projects returned from this connection" filterBy: ProjectV2Filters = { + + } + , "The query to search projects by." query: String = ""): ProjectV2Connection! + + """ + A list of repositories this team has access to. + """ + repositories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The search string to look for. Repositories will be returned where the name contains your search string." query: String, "Order for the connection." orderBy: TeamRepositoryOrder): TeamRepositoryConnection! + + """ + The HTTP path for this team's repositories + """ + repositoriesResourcePath: URI! + + """ + The HTTP URL for this team's repositories + """ + repositoriesUrl: URI! + + """ + The HTTP path for this team + """ + resourcePath: URI! + + """ + What algorithm is used for review assignment for this team + """ + reviewRequestDelegationAlgorithm: TeamReviewAssignmentAlgorithm + + """ + True if review assignment is enabled for this team + """ + reviewRequestDelegationEnabled: Boolean! + + """ + How many team members are required for review assignment for this team + """ + reviewRequestDelegationMemberCount: Int + + """ + When assigning team members via delegation, whether the entire team should be notified as well. + """ + reviewRequestDelegationNotifyTeam: Boolean! + + """ + The slug corresponding to the team. + """ + slug: String! + + """ + The HTTP path for this team's teams + """ + teamsResourcePath: URI! + + """ + The HTTP URL for this team's teams + """ + teamsUrl: URI! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this team + """ + url: URI! + + """ + Team is adminable by the viewer. + """ + viewerCanAdminister: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +Audit log entry for a team.add_member event. +""" +type TeamAddMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the TeamAddMemberAuditEntry object + """ + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.add_repository event. +""" +type TeamAddRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the TeamAddRepositoryAuditEntry object + """ + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Metadata for an audit entry with action team.* +""" +interface TeamAuditEntryData { + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI +} + +""" +Audit log entry for a team.change_parent_team event. +""" +type TeamChangeParentTeamAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the TeamChangeParentTeamAuditEntry object + """ + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The new parent team. + """ + parentTeam: Team + + """ + The name of the new parent team + """ + parentTeamName: String + + """ + The name of the former parent team + """ + parentTeamNameWas: String + + """ + The HTTP path for the parent team + """ + parentTeamResourcePath: URI + + """ + The HTTP URL for the parent team + """ + parentTeamUrl: URI + + """ + The former parent team. + """ + parentTeamWas: Team + + """ + The HTTP path for the previous parent team + """ + parentTeamWasResourcePath: URI + + """ + The HTTP URL for the previous parent team + """ + parentTeamWasUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Team. +""" +type TeamConnection { + """ + A list of edges. + """ + edges: [TeamEdge] + + """ + A list of nodes. + """ + nodes: [Team] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +A team discussion. +""" +type TeamDiscussion implements Comment & Deletable & Node & Reactable & Subscribable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the discussion's team. + """ + authorAssociation: CommentAuthorAssociation! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + Identifies the discussion body hash. + """ + bodyVersion: String! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + A list of comments on this discussion. + """ + comments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: TeamDiscussionCommentOrder, "When provided, filters the connection such that results begin with the comment with this number." fromComment: Int): TeamDiscussionCommentConnection! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The HTTP path for discussion comments + """ + commentsResourcePath: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The HTTP URL for discussion comments + """ + commentsUrl: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the TeamDiscussion object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + Whether or not the discussion is pinned. + """ + isPinned: Boolean! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Whether or not the discussion is only visible to team members and organization owners. + """ + isPrivate: Boolean! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the discussion within its team. + """ + number: Int! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The HTTP path for this discussion + """ + resourcePath: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The team that defines the context of this discussion. + """ + team: Team! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The title of the discussion + """ + title: String! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this discussion + """ + url: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Whether or not the current viewer can pin this discussion. + """ + viewerCanPin: Boolean! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the viewer is able to change their subscription status for the repository. + """ + viewerCanSubscribe: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! + + """ + Identifies if the viewer is watching, not watching, or ignoring the subscribable entity. + """ + viewerSubscription: SubscriptionState +} + +""" +A comment on a team discussion. +""" +type TeamDiscussionComment implements Comment & Deletable & Node & Reactable & UniformResourceLocatable & Updatable & UpdatableComment { + """ + The actor who authored the comment. + """ + author: Actor + + """ + Author's association with the comment's team. + """ + authorAssociation: CommentAuthorAssociation! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The body as Markdown. + """ + body: String! + + """ + The body rendered to HTML. + """ + bodyHTML: HTML! + + """ + The body rendered to text. + """ + bodyText: String! + + """ + The current version of the body content. + """ + bodyVersion: String! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Check if this comment was created via an email reply. + """ + createdViaEmail: Boolean! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The discussion this comment is about. + """ + discussion: TeamDiscussion! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + The actor who edited the comment. + """ + editor: Actor + + """ + The Node ID of the TeamDiscussionComment object + """ + id: ID! + + """ + Check if this comment was edited and includes an edit with the creation data + """ + includesCreatedEdit: Boolean! + + """ + The moment the editor made the last edit + """ + lastEditedAt: DateTime + + """ + Identifies the comment number. + """ + number: Int! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies when the comment was published at. + """ + publishedAt: DateTime + + """ + A list of reactions grouped by content left on the subject. + """ + reactionGroups: [ReactionGroup!] + + """ + A list of Reactions left on the Issue. + """ + reactions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Allows filtering Reactions by emoji." content: ReactionContent, "Allows specifying the order in which reactions are returned." orderBy: ReactionOrder): ReactionConnection! + + """ + The HTTP path for this comment + """ + resourcePath: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this comment + """ + url: URI! @deprecated(reason: "The Team Discussions feature is deprecated in favor of Organization Discussions. Follow the guide at https:\/\/github.blog\/changelog\/2023-02-08-sunset-notice-team-discussions\/ to find a suitable replacement. Removal on 2024-07-01 UTC.") + + """ + A list of edits to this content. + """ + userContentEdits("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserContentEditConnection + + """ + Check if the current viewer can delete this object. + """ + viewerCanDelete: Boolean! + + """ + Can user react to this subject + """ + viewerCanReact: Boolean! + + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! + + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! + + """ + Did the viewer author this comment. + """ + viewerDidAuthor: Boolean! +} + +""" +The connection type for TeamDiscussionComment. +""" +type TeamDiscussionCommentConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionCommentEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussionComment] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionCommentEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussionComment +} + +""" +Ways in which team discussion comment connections can be ordered. +""" +input TeamDiscussionCommentOrder { + """ + The field by which to order nodes. + """ + field: TeamDiscussionCommentOrderField! + + """ + The direction in which to order nodes. + """ + direction: OrderDirection! +} + +""" +Properties by which team discussion comment connections can be ordered. +""" +enum TeamDiscussionCommentOrderField { + """ + Allows sequential ordering of team discussion comments (which is equivalent to chronological ordering). + """ + NUMBER +} + +""" +The connection type for TeamDiscussion. +""" +type TeamDiscussionConnection { + """ + A list of edges. + """ + edges: [TeamDiscussionEdge] + + """ + A list of nodes. + """ + nodes: [TeamDiscussion] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type TeamDiscussionEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: TeamDiscussion +} + +""" +Ways in which team discussion connections can be ordered. +""" +input TeamDiscussionOrder { + """ + The field by which to order nodes. + """ + field: TeamDiscussionOrderField! + + """ + The direction in which to order nodes. + """ + direction: OrderDirection! +} + +""" +Properties by which team discussion connections can be ordered. +""" +enum TeamDiscussionOrderField { + """ + Allows chronological ordering of team discussions. + """ + CREATED_AT +} + +""" +An edge in a connection. +""" +type TeamEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Team +} + +""" +The connection type for User. +""" +type TeamMemberConnection { + """ + A list of edges. + """ + edges: [TeamMemberEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a user who is a member of a team. +""" +type TeamMemberEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The HTTP path to the organization's member access page. + """ + memberAccessResourcePath: URI! + + """ + The HTTP URL to the organization's member access page. + """ + memberAccessUrl: URI! + + node: User! + + """ + The role the member has on the team. + """ + role: TeamMemberRole! +} + +""" +Ordering options for team member connections +""" +input TeamMemberOrder { + """ + The field to order team members by. + """ + field: TeamMemberOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which team member connections can be ordered. +""" +enum TeamMemberOrderField { + """ + Order team members by login + """ + LOGIN + + """ + Order team members by creation time + """ + CREATED_AT +} + +""" +The possible team member roles; either 'maintainer' or 'member'. +""" +enum TeamMemberRole { + """ + A team maintainer has permission to add and remove team members. + """ + MAINTAINER + + """ + A team member has no administrative permissions on the team. + """ + MEMBER +} + +""" +Defines which types of team members are included in the returned list. Can be one of IMMEDIATE, CHILD_TEAM or ALL. +""" +enum TeamMembershipType { + """ + Includes only immediate members of the team. + """ + IMMEDIATE + + """ + Includes only child team members for the team. + """ + CHILD_TEAM + + """ + Includes immediate and child team members for the team. + """ + ALL +} + +""" +The possible team notification values. +""" +enum TeamNotificationSetting { + """ + Everyone will receive notifications when the team is @mentioned. + """ + NOTIFICATIONS_ENABLED + + """ + No one will receive notifications. + """ + NOTIFICATIONS_DISABLED +} + +""" +Ways in which team connections can be ordered. +""" +input TeamOrder { + """ + The field in which to order nodes by. + """ + field: TeamOrderField! + + """ + The direction in which to order nodes. + """ + direction: OrderDirection! +} + +""" +Properties by which team connections can be ordered. +""" +enum TeamOrderField { + """ + Allows ordering a list of teams by name. + """ + NAME +} + +""" +The possible team privacy values. +""" +enum TeamPrivacy { + """ + A secret team can only be seen by its members. + """ + SECRET + + """ + A visible team can be seen and @mentioned by every member of the organization. + """ + VISIBLE +} + +""" +Audit log entry for a team.remove_member event. +""" +type TeamRemoveMemberAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the TeamRemoveMemberAuditEntry object + """ + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +Audit log entry for a team.remove_repository event. +""" +type TeamRemoveRepositoryAuditEntry implements AuditEntry & Node & OrganizationAuditEntryData & RepositoryAuditEntryData & TeamAuditEntryData { + """ + The action name + """ + action: String! + + """ + The user who initiated the action + """ + actor: AuditEntryActor + + """ + The IP address of the actor + """ + actorIp: String + + """ + A readable representation of the actor's location + """ + actorLocation: ActorLocation + + """ + The username of the user who initiated the action + """ + actorLogin: String + + """ + The HTTP path for the actor. + """ + actorResourcePath: URI + + """ + The HTTP URL for the actor. + """ + actorUrl: URI + + """ + The time the action was initiated + """ + createdAt: PreciseDateTime! + + """ + The Node ID of the TeamRemoveRepositoryAuditEntry object + """ + id: ID! + + """ + Whether the team was mapped to an LDAP Group. + """ + isLdapMapped: Boolean + + """ + The corresponding operation type for the action + """ + operationType: OperationType + + """ + The Organization associated with the Audit Entry. + """ + organization: Organization + + """ + The name of the Organization. + """ + organizationName: String + + """ + The HTTP path for the organization + """ + organizationResourcePath: URI + + """ + The HTTP URL for the organization + """ + organizationUrl: URI + + """ + The repository associated with the action + """ + repository: Repository + + """ + The name of the repository + """ + repositoryName: String + + """ + The HTTP path for the repository + """ + repositoryResourcePath: URI + + """ + The HTTP URL for the repository + """ + repositoryUrl: URI + + """ + The team associated with the action + """ + team: Team + + """ + The name of the team + """ + teamName: String + + """ + The HTTP path for this team + """ + teamResourcePath: URI + + """ + The HTTP URL for this team + """ + teamUrl: URI + + """ + The user affected by the action + """ + user: User + + """ + For actions involving two users, the actor is the initiator and the user is the affected user. + """ + userLogin: String + + """ + The HTTP path for the user. + """ + userResourcePath: URI + + """ + The HTTP URL for the user. + """ + userUrl: URI +} + +""" +The connection type for Repository. +""" +type TeamRepositoryConnection { + """ + A list of edges. + """ + edges: [TeamRepositoryEdge] + + """ + A list of nodes. + """ + nodes: [Repository] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +Represents a team repository. +""" +type TeamRepositoryEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + node: Repository! + + """ + The permission level the team has on the repository + """ + permission: RepositoryPermission! +} + +""" +Ordering options for team repository connections +""" +input TeamRepositoryOrder { + """ + The field to order repositories by. + """ + field: TeamRepositoryOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which team repository connections can be ordered. +""" +enum TeamRepositoryOrderField { + """ + Order repositories by creation time + """ + CREATED_AT + + """ + Order repositories by update time + """ + UPDATED_AT + + """ + Order repositories by push time + """ + PUSHED_AT + + """ + Order repositories by name + """ + NAME + + """ + Order repositories by permission + """ + PERMISSION + + """ + Order repositories by number of stargazers + """ + STARGAZERS +} + +""" +The possible team review assignment algorithms +""" +enum TeamReviewAssignmentAlgorithm { + """ + Alternate reviews between each team member + """ + ROUND_ROBIN + + """ + Balance review load across the entire team + """ + LOAD_BALANCE +} + +""" +The role of a user on a team. +""" +enum TeamRole { + """ + User has admin rights on the team. + """ + ADMIN + + """ + User is a member of the team. + """ + MEMBER +} + +""" +A text match within a search result. +""" +type TextMatch { + """ + The specific text fragment within the property matched on. + """ + fragment: String! + + """ + Highlights within the matched fragment. + """ + highlights: [TextMatchHighlight!]! + + """ + The property matched on. + """ + property: String! +} + +""" +Represents a single highlight in a search result match. +""" +type TextMatchHighlight { + """ + The indice in the fragment where the matched text begins. + """ + beginIndice: Int! + + """ + The indice in the fragment where the matched text ends. + """ + endIndice: Int! + + """ + The text matched. + """ + text: String! +} + +""" +The possible states of a thread subscription form action +""" +enum ThreadSubscriptionFormAction { + """ + The User cannot subscribe or unsubscribe to the thread + """ + NONE + + """ + The User can subscribe to the thread + """ + SUBSCRIBE + + """ + The User can unsubscribe to the thread + """ + UNSUBSCRIBE +} + +""" +The possible states of a subscription. +""" +enum ThreadSubscriptionState { + """ + The subscription status is currently unavailable. + """ + UNAVAILABLE + + """ + The subscription status is currently disabled. + """ + DISABLED + + """ + The User is never notified because they are ignoring the list + """ + IGNORING_LIST + + """ + The User is notified because they chose custom settings for this thread. + """ + SUBSCRIBED_TO_THREAD_EVENTS + + """ + The User is never notified because they are ignoring the thread + """ + IGNORING_THREAD + + """ + The User is notified becuase they are watching the list + """ + SUBSCRIBED_TO_LIST + + """ + The User is notified because they chose custom settings for this thread. + """ + SUBSCRIBED_TO_THREAD_TYPE + + """ + The User is notified because they are subscribed to the thread + """ + SUBSCRIBED_TO_THREAD + + """ + The User is not recieving notifications from this thread + """ + NONE +} + +""" +A topic aggregates entities that are related to a subject. +""" +type Topic implements Node & Starrable { + """ + The Node ID of the Topic object + """ + id: ID! + + """ + The topic's name. + """ + name: String! + + """ + A list of related topics, including aliases of this topic, sorted with the most relevant + first. Returns up to 10 Topics. + """ + relatedTopics("How many topics to return." first: Int = 3): [Topic!]! + + """ + A list of repositories. + """ + repositories("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Array of viewer's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the current viewer owns." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If true, only repositories whose owner can be sponsored via GitHub Sponsors will be returned." sponsorableOnly: Boolean = false): RepositoryConnection! + + """ + Returns a count of how many stargazers there are on this object + """ + stargazerCount: Int! + + """ + A list of users who have starred this starrable. + """ + stargazers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Order for connection" orderBy: StarOrder): StargazerConnection! + + """ + Returns a boolean indicating whether the viewing user has starred this starrable. + """ + viewerHasStarred: Boolean! +} + +""" +Metadata for an audit entry with a topic. +""" +interface TopicAuditEntryData { + """ + The name of the topic added to the repository + """ + topic: Topic + + """ + The name of the topic added to the repository + """ + topicName: String +} + +""" +Reason that the suggested topic is declined. +""" +enum TopicSuggestionDeclineReason { + """ + The suggested topic is not relevant to the repository. + """ + NOT_RELEVANT @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") + + """ + The suggested topic is too specific for the repository (e.g. #ruby-on-rails-version-4-2-1). + """ + TOO_SPECIFIC @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") + + """ + The viewer does not like the suggested topic. + """ + PERSONAL_PREFERENCE @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") + + """ + The suggested topic is too general for the repository. + """ + TOO_GENERAL @deprecated(reason: "Suggested topics are no longer supported Removal on 2024-04-01 UTC.") +} + +""" +The possible states of a tracked issue. +""" +enum TrackedIssueStates { + """ + The tracked issue is open + """ + OPEN + + """ + The tracked issue is closed + """ + CLOSED +} + +""" +Autogenerated input type of TransferEnterpriseOrganization +""" +input TransferEnterpriseOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization to transfer. + """ + organizationId: ID! + + """ + The ID of the enterprise where the organization should be transferred. + """ + destinationEnterpriseId: ID! +} + +""" +Autogenerated return type of TransferEnterpriseOrganization. +""" +type TransferEnterpriseOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization for which a transfer was initiated. + """ + organization: Organization +} + +""" +Autogenerated input type of TransferIssue +""" +input TransferIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the issue to be transferred + """ + issueId: ID! + + """ + The Node ID of the repository the issue should be transferred to + """ + repositoryId: ID! + + """ + Whether to create labels if they don't exist in the target repository (matched by name) + """ + createLabelsIfMissing: Boolean = false +} + +""" +Autogenerated return type of TransferIssue. +""" +type TransferIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue that was transferred + """ + issue: Issue +} + +""" +Represents a 'transferred' event on a given issue or pull request. +""" +type TransferredEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The repository this came from + """ + fromRepository: Repository + + """ + The Node ID of the TransferredEvent object + """ + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Represents a Git tree. +""" +type Tree implements GitObject & Node { + """ + An abbreviated version of the Git object ID + """ + abbreviatedOid: String! + + """ + The HTTP path for this Git object + """ + commitResourcePath: URI! + + """ + The HTTP URL for this Git object + """ + commitUrl: URI! + + """ + A list of tree entries. + """ + entries: [TreeEntry!] + + """ + The Node ID of the Tree object + """ + id: ID! + + """ + The Git object ID + """ + oid: GitObjectID! + + """ + The Repository the Git object belongs to + """ + repository: Repository! +} + +""" +Represents a Git tree entry. +""" +type TreeEntry { + """ + The extension of the file + """ + extension: String + + """ + Whether or not this tree entry is generated + """ + isGenerated: Boolean! + + """ + The programming language this file is written in. + """ + language: Language + + """ + Number of lines in the file. + """ + lineCount: Int + + """ + Entry file mode. + """ + mode: Int! + + """ + Entry file name. + """ + name: String! + + """ + Entry file name. (Base64-encoded) + """ + nameRaw: Base64String! + + """ + Entry file object. + """ + object: GitObject + + """ + Entry file Git object ID. + """ + oid: GitObjectID! + + """ + The full path of the file. + """ + path: String + + """ + The full path of the file. (Base64-encoded) + """ + pathRaw: Base64String + + """ + The Repository the tree entry belongs to + """ + repository: Repository! + + """ + Entry byte size + """ + size: Int! + + """ + If the TreeEntry is for a directory occupied by a submodule project, this returns the corresponding submodule + """ + submodule: Submodule + + """ + Entry file type. + """ + type: String! +} + +""" +An RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI string. +""" +scalar URI + +""" +Autogenerated input type of UnarchiveProjectV2Item +""" +input UnarchiveProjectV2ItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to archive the item from. + """ + projectId: ID! + + """ + The ID of the ProjectV2Item to unarchive. + """ + itemId: ID! +} + +""" +Autogenerated return type of UnarchiveProjectV2Item. +""" +type UnarchiveProjectV2ItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item unarchived from the project. + """ + item: ProjectV2Item +} + +""" +Autogenerated input type of UnarchiveRepository +""" +input UnarchiveRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to unarchive. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of UnarchiveRepository. +""" +type UnarchiveRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was unarchived. + """ + repository: Repository +} + +""" +Represents an 'unassigned' event on any assignable object. +""" +type UnassignedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the assignable associated with the event. + """ + assignable: Assignable! + + """ + Identifies the user or mannequin that was unassigned. + """ + assignee: Assignee + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UnassignedEvent object + """ + id: ID! + + """ + Identifies the subject (user) who was unassigned. + """ + user: User @deprecated(reason: "Assignees can now be mannequins. Use the `assignee` field instead. Removal on 2020-01-01 UTC.") +} + +""" +Autogenerated input type of UnfollowOrganization +""" +input UnfollowOrganizationInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the organization to unfollow. + """ + organizationId: ID! +} + +""" +Autogenerated return type of UnfollowOrganization. +""" +type UnfollowOrganizationPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The organization that was unfollowed. + """ + organization: Organization +} + +""" +Autogenerated input type of UnfollowUser +""" +input UnfollowUserInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the user to unfollow. + """ + userId: ID! +} + +""" +Autogenerated return type of UnfollowUser. +""" +type UnfollowUserPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The user that was unfollowed. + """ + user: User +} + +""" +Represents a type that can be retrieved by a URL. +""" +interface UniformResourceLocatable { + """ + The HTML path to this resource. + """ + resourcePath: URI! + + """ + The URL to this resource. + """ + url: URI! +} + +""" +Represents an unknown signature on a Commit or Tag. +""" +type UnknownSignature implements GitSignature { + """ + Email used to sign this object. + """ + email: String! + + """ + True if the signature is valid and verified by GitHub. + """ + isValid: Boolean! + + """ + Payload for GPG signing object. Raw ODB object without the signature header. + """ + payload: String! + + """ + ASCII-armored signature header from object. + """ + signature: String! + + """ + GitHub user corresponding to the email signing this commit. + """ + signer: User + + """ + The state of this signature. `VALID` if signature is valid and verified by GitHub, otherwise represents reason why signature is considered invalid. + """ + state: GitSignatureState! + + """ + True if the signature was made with GitHub's signing key. + """ + wasSignedByGitHub: Boolean! +} + +""" +Represents an 'unlabeled' event on a given issue or pull request. +""" +type UnlabeledEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UnlabeledEvent object + """ + id: ID! + + """ + Identifies the label associated with the 'unlabeled' event. + """ + label: Label! + + """ + Identifies the `Labelable` associated with the event. + """ + labelable: Labelable! +} + +""" +Autogenerated input type of UnlinkProjectV2FromRepository +""" +input UnlinkProjectV2FromRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to unlink from the repository. + """ + projectId: ID! + + """ + The ID of the repository to unlink from the project. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of UnlinkProjectV2FromRepository. +""" +type UnlinkProjectV2FromRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository the project is no longer linked to. + """ + repository: Repository +} + +""" +Autogenerated input type of UnlinkProjectV2FromTeam +""" +input UnlinkProjectV2FromTeamInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to unlink from the team. + """ + projectId: ID! + + """ + The ID of the team to unlink from the project. + """ + teamId: ID! +} + +""" +Autogenerated return type of UnlinkProjectV2FromTeam. +""" +type UnlinkProjectV2FromTeamPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team the project is unlinked from + """ + team: Team +} + +""" +Autogenerated input type of UnlinkRepositoryFromProject +""" +input UnlinkRepositoryFromProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project linked to the Repository. + """ + projectId: ID! + + """ + The ID of the Repository linked to the Project. + """ + repositoryId: ID! +} + +""" +Autogenerated return type of UnlinkRepositoryFromProject. +""" +type UnlinkRepositoryFromProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The linked Project. + """ + project: Project + + """ + The linked Repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UnlockLockable +""" +input UnlockLockableInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the item to be unlocked. + """ + lockableId: ID! +} + +""" +Autogenerated return type of UnlockLockable. +""" +type UnlockLockablePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was unlocked. + """ + unlockedRecord: Lockable +} + +""" +Represents an 'unlocked' event on a given issue or pull request. +""" +type UnlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UnlockedEvent object + """ + id: ID! + + """ + Object that was unlocked. + """ + lockable: Lockable! +} + +""" +Autogenerated input type of UnmarkDiscussionCommentAsAnswer +""" +input UnmarkDiscussionCommentAsAnswerInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to unmark as an answer. + """ + id: ID! +} + +""" +Autogenerated return type of UnmarkDiscussionCommentAsAnswer. +""" +type UnmarkDiscussionCommentAsAnswerPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The discussion that includes the comment. + """ + discussion: Discussion +} + +""" +Autogenerated input type of UnmarkFileAsViewed +""" +input UnmarkFileAsViewedInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! + + """ + The path of the file to mark as unviewed + """ + path: String! +} + +""" +Autogenerated return type of UnmarkFileAsViewed. +""" +type UnmarkFileAsViewedPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UnmarkIssueAsDuplicate +""" +input UnmarkIssueAsDuplicateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + ID of the issue or pull request currently marked as a duplicate. + """ + duplicateId: ID! + + """ + ID of the issue or pull request currently considered canonical/authoritative/original. + """ + canonicalId: ID! +} + +""" +Autogenerated return type of UnmarkIssueAsDuplicate. +""" +type UnmarkIssueAsDuplicatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue or pull request that was marked as a duplicate. + """ + duplicate: IssueOrPullRequest +} + +""" +Autogenerated input type of UnmarkProjectV2AsTemplate +""" +input UnmarkProjectV2AsTemplateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to unmark as a template. + """ + projectId: ID! +} + +""" +Autogenerated return type of UnmarkProjectV2AsTemplate. +""" +type UnmarkProjectV2AsTemplatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The project. + """ + projectV2: ProjectV2 +} + +""" +Represents an 'unmarked_as_duplicate' event on a given issue or pull request. +""" +type UnmarkedAsDuplicateEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + The authoritative issue or pull request which has been duplicated by another. + """ + canonical: IssueOrPullRequest + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The issue or pull request which has been marked as a duplicate of another. + """ + duplicate: IssueOrPullRequest + + """ + The Node ID of the UnmarkedAsDuplicateEvent object + """ + id: ID! + + """ + Canonical and duplicate belong to different repositories. + """ + isCrossRepository: Boolean! +} + +""" +Autogenerated input type of UnminimizeComment +""" +input UnminimizeCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subject to modify. + """ + subjectId: ID! +} + +""" +Autogenerated return type of UnminimizeComment. +""" +type UnminimizeCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The comment that was unminimized. + """ + unminimizedComment: Minimizable +} + +""" +Autogenerated input type of UnpinIssue +""" +input UnpinIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the issue to be unpinned + """ + issueId: ID! +} + +""" +Autogenerated return type of UnpinIssue. +""" +type UnpinIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The id of the pinned issue that was unpinned + """ + id: ID + + """ + The issue that was unpinned + """ + issue: Issue +} + +""" +Represents an 'unpinned' event on a given issue or pull request. +""" +type UnpinnedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UnpinnedEvent object + """ + id: ID! + + """ + Identifies the issue associated with the event. + """ + issue: Issue! +} + +""" +Autogenerated input type of UnresolveReviewThread +""" +input UnresolveReviewThreadInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the thread to unresolve + """ + threadId: ID! +} + +""" +Autogenerated return type of UnresolveReviewThread. +""" +type UnresolveReviewThreadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The thread to resolve. + """ + thread: PullRequestReviewThread +} + +""" +Autogenerated input type of UnsubscribeFromNotifications +""" +input UnsubscribeFromNotificationsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The NotificationThread IDs of the objects to unsubscribe from. + """ + ids: [ID!]! +} + +""" +Autogenerated return type of UnsubscribeFromNotifications. +""" +type UnsubscribeFromNotificationsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Did the operation succeed? + """ + success: Boolean +} + +""" +Represents an 'unsubscribed' event on a given `Subscribable`. +""" +type UnsubscribedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UnsubscribedEvent object + """ + id: ID! + + """ + Object referenced by event. + """ + subscribable: Subscribable! +} + +""" +Entities that can be updated. +""" +interface Updatable { + """ + Check if the current viewer can update this object. + """ + viewerCanUpdate: Boolean! +} + +""" +Comments that can be updated. +""" +interface UpdatableComment { + """ + Reasons why the current viewer can not update this comment. + """ + viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! +} + +""" +Autogenerated input type of UpdateBranchProtectionRule +""" +input UpdateBranchProtectionRuleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the branch protection rule to be updated. + """ + branchProtectionRuleId: ID! + + """ + The glob-like pattern used to determine matching branches. + """ + pattern: String + + """ + Are approving reviews required to update matching branches. + """ + requiresApprovingReviews: Boolean + + """ + Number of approving reviews required to update matching branches. + """ + requiredApprovingReviewCount: Int + + """ + Are commits required to be signed. + """ + requiresCommitSignatures: Boolean + + """ + Are merge commits prohibited from being pushed to this branch. + """ + requiresLinearHistory: Boolean + + """ + Is branch creation a protected operation. + """ + blocksCreations: Boolean + + """ + Are force pushes allowed on this branch. + """ + allowsForcePushes: Boolean + + """ + Can this branch be deleted. + """ + allowsDeletions: Boolean + + """ + Can admins override branch protection. + """ + isAdminEnforced: Boolean + + """ + Are status checks required to update matching branches. + """ + requiresStatusChecks: Boolean + + """ + Are branches required to be up to date before merging. + """ + requiresStrictStatusChecks: Boolean + + """ + Are reviews from code owners required to update matching branches. + """ + requiresCodeOwnerReviews: Boolean + + """ + Will new commits pushed to matching branches dismiss pull request review approvals. + """ + dismissesStaleReviews: Boolean + + """ + Is dismissal of pull request reviews restricted. + """ + restrictsReviewDismissals: Boolean + + """ + A list of User, Team, or App IDs allowed to dismiss reviews on pull requests targeting matching branches. + """ + reviewDismissalActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass pull requests targeting matching branches. + """ + bypassPullRequestActorIds: [ID!] + + """ + A list of User, Team, or App IDs allowed to bypass force push targeting matching branches. + """ + bypassForcePushActorIds: [ID!] + + """ + Is pushing to matching branches restricted. + """ + restrictsPushes: Boolean + + """ + A list of User, Team, or App IDs allowed to push to matching branches. + """ + pushActorIds: [ID!] + + """ + List of required status check contexts that must pass for commits to be accepted to matching branches. + """ + requiredStatusCheckContexts: [String!] + + """ + The list of required status checks + """ + requiredStatusChecks: [RequiredStatusCheckInput!] + + """ + Are successful deployments required before merging. + """ + requiresDeployments: Boolean + + """ + The list of required deployment environments + """ + requiredDeploymentEnvironments: [String!] + + """ + Are conversations required to be resolved before merging. + """ + requiresConversationResolution: Boolean + + """ + Whether the most recent push must be approved by someone other than the person who pushed it + """ + requireLastPushApproval: Boolean + + """ + Whether to set the branch as read-only. If this is true, users will not be able to push to the branch. + """ + lockBranch: Boolean + + """ + Whether users can pull changes from upstream when the branch is locked. Set to `true` to allow fork syncing. Set to `false` to prevent fork syncing. + """ + lockAllowsFetchAndMerge: Boolean +} + +""" +Autogenerated return type of UpdateBranchProtectionRule. +""" +type UpdateBranchProtectionRulePayload { + """ + The newly created BranchProtectionRule. + """ + branchProtectionRule: BranchProtectionRule + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckRun +""" +input UpdateCheckRunInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the repository. + """ + repositoryId: ID! + + """ + The node of the check. + """ + checkRunId: ID! + + """ + The name of the check. + """ + name: String + + """ + The URL of the integrator's site that has the full details of the check. + """ + detailsUrl: URI + + """ + A reference for the run on the integrator's system. + """ + externalId: String + + """ + The current status. + """ + status: RequestableCheckStatusState + + """ + The time that the check run began. + """ + startedAt: DateTime + + """ + The final conclusion of the check. + """ + conclusion: CheckConclusionState + + """ + The time that the check run finished. + """ + completedAt: DateTime + + """ + Descriptive details about the run. + """ + output: CheckRunOutput + + """ + Possible further actions the integrator can perform, which a user may trigger. + """ + actions: [CheckRunAction!] +} + +""" +Autogenerated return type of UpdateCheckRun. +""" +type UpdateCheckRunPayload { + """ + The updated check run. + """ + checkRun: CheckRun + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateCheckSuitePreferences +""" +input UpdateCheckSuitePreferencesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + The check suite preferences to modify. + """ + autoTriggerPreferences: [CheckSuiteAutoTriggerPreference!]! +} + +""" +Autogenerated return type of UpdateCheckSuitePreferences. +""" +type UpdateCheckSuitePreferencesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateDiscussionComment +""" +input UpdateDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion comment to update. + """ + commentId: ID! + + """ + The new contents of the comment body. + """ + body: String! +} + +""" +Autogenerated return type of UpdateDiscussionComment. +""" +type UpdateDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The modified discussion comment. + """ + comment: DiscussionComment +} + +""" +Autogenerated input type of UpdateDiscussion +""" +input UpdateDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to update. + """ + discussionId: ID! + + """ + The new discussion title. + """ + title: String + + """ + The new contents of the discussion body. + """ + body: String + + """ + The Node ID of a discussion category within the same repository to change this discussion to. + """ + categoryId: ID +} + +""" +Autogenerated return type of UpdateDiscussion. +""" +type UpdateDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The modified discussion. + """ + discussion: Discussion +} + +""" +Autogenerated input type of UpdateEnterpriseAdministratorRole +""" +input UpdateEnterpriseAdministratorRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the admin belongs to. + """ + enterpriseId: ID! + + """ + The login of a administrator whose role is being changed. + """ + login: String! + + """ + The new role for the Enterprise administrator. + """ + role: EnterpriseAdministratorRole! +} + +""" +Autogenerated return type of UpdateEnterpriseAdministratorRole. +""" +type UpdateEnterpriseAdministratorRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of changing the administrator's role. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting +""" +input UpdateEnterpriseAllowPrivateRepositoryForkingSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the allow private repository forking setting. + """ + enterpriseId: ID! + + """ + The value for the allow private repository forking setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! + + """ + The value for the allow private repository forking policy on the enterprise. + """ + policyValue: EnterpriseAllowPrivateRepositoryForkingPolicyValue +} + +""" +Autogenerated return type of UpdateEnterpriseAllowPrivateRepositoryForkingSetting. +""" +type UpdateEnterpriseAllowPrivateRepositoryForkingSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated allow private repository forking setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the allow private repository forking setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseDefaultRepositoryPermissionSetting +""" +input UpdateEnterpriseDefaultRepositoryPermissionSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the base repository permission setting. + """ + enterpriseId: ID! + + """ + The value for the base repository permission setting on the enterprise. + """ + settingValue: EnterpriseDefaultRepositoryPermissionSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseDefaultRepositoryPermissionSetting. +""" +type UpdateEnterpriseDefaultRepositoryPermissionSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated base repository permission setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the base repository permission setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting +""" +input UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can change repository visibility setting. + """ + enterpriseId: ID! + + """ + The value for the members can change repository visibility setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanChangeRepositoryVisibilitySetting. +""" +type UpdateEnterpriseMembersCanChangeRepositoryVisibilitySettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can change repository visibility setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can change repository visibility setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanCreateRepositoriesSetting +""" +input UpdateEnterpriseMembersCanCreateRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can create repositories setting. + """ + enterpriseId: ID! + + """ + Value for the members can create repositories setting on the enterprise. This or the granular public/private/internal allowed fields (but not both) must be provided. + """ + settingValue: EnterpriseMembersCanCreateRepositoriesSettingValue + + """ + When false, allow member organizations to set their own repository creation member privileges. + """ + membersCanCreateRepositoriesPolicyEnabled: Boolean + + """ + Allow members to create public repositories. Defaults to current value. + """ + membersCanCreatePublicRepositories: Boolean + + """ + Allow members to create private repositories. Defaults to current value. + """ + membersCanCreatePrivateRepositories: Boolean + + """ + Allow members to create internal repositories. Defaults to current value. + """ + membersCanCreateInternalRepositories: Boolean +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanCreateRepositoriesSetting. +""" +type UpdateEnterpriseMembersCanCreateRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can create repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can create repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteIssuesSetting +""" +input UpdateEnterpriseMembersCanDeleteIssuesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete issues setting. + """ + enterpriseId: ID! + + """ + The value for the members can delete issues setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteIssuesSetting. +""" +type UpdateEnterpriseMembersCanDeleteIssuesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete issues setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete issues setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting +""" +input UpdateEnterpriseMembersCanDeleteRepositoriesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can delete repositories setting. + """ + enterpriseId: ID! + + """ + The value for the members can delete repositories setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanDeleteRepositoriesSetting. +""" +type UpdateEnterpriseMembersCanDeleteRepositoriesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can delete repositories setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can delete repositories setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting +""" +input UpdateEnterpriseMembersCanInviteCollaboratorsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can invite collaborators setting. + """ + enterpriseId: ID! + + """ + The value for the members can invite collaborators setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanInviteCollaboratorsSetting. +""" +type UpdateEnterpriseMembersCanInviteCollaboratorsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can invite collaborators setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can invite collaborators setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanMakePurchasesSetting +""" +input UpdateEnterpriseMembersCanMakePurchasesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can make purchases setting. + """ + enterpriseId: ID! + + """ + The value for the members can make purchases setting on the enterprise. + """ + settingValue: EnterpriseMembersCanMakePurchasesSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanMakePurchasesSetting. +""" +type UpdateEnterpriseMembersCanMakePurchasesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can make purchases setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can make purchases setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting +""" +input UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can update protected branches setting. + """ + enterpriseId: ID! + + """ + The value for the members can update protected branches setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanUpdateProtectedBranchesSetting. +""" +type UpdateEnterpriseMembersCanUpdateProtectedBranchesSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can update protected branches setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can update protected branches setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting +""" +input UpdateEnterpriseMembersCanViewDependencyInsightsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the members can view dependency insights setting. + """ + enterpriseId: ID! + + """ + The value for the members can view dependency insights setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseMembersCanViewDependencyInsightsSetting. +""" +type UpdateEnterpriseMembersCanViewDependencyInsightsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated members can view dependency insights setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the members can view dependency insights setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseOrganizationProjectsSetting +""" +input UpdateEnterpriseOrganizationProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the organization projects setting. + """ + enterpriseId: ID! + + """ + The value for the organization projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseOrganizationProjectsSetting. +""" +type UpdateEnterpriseOrganizationProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated organization projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the organization projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseOwnerOrganizationRole +""" +input UpdateEnterpriseOwnerOrganizationRoleInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Enterprise which the owner belongs to. + """ + enterpriseId: ID! + + """ + The ID of the organization for membership change. + """ + organizationId: ID! + + """ + The role to assume in the organization. + """ + organizationRole: RoleInOrganization! +} + +""" +Autogenerated return type of UpdateEnterpriseOwnerOrganizationRole. +""" +type UpdateEnterpriseOwnerOrganizationRolePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of changing the owner's organization role. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseProfile +""" +input UpdateEnterpriseProfileInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Enterprise ID to update. + """ + enterpriseId: ID! + + """ + The name of the enterprise. + """ + name: String + + """ + The description of the enterprise. + """ + description: String + + """ + The URL of the enterprise's website. + """ + websiteUrl: String + + """ + The location of the enterprise. + """ + location: String +} + +""" +Autogenerated return type of UpdateEnterpriseProfile. +""" +type UpdateEnterpriseProfilePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated enterprise. + """ + enterprise: Enterprise +} + +""" +Autogenerated input type of UpdateEnterpriseRepositoryProjectsSetting +""" +input UpdateEnterpriseRepositoryProjectsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the repository projects setting. + """ + enterpriseId: ID! + + """ + The value for the repository projects setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseRepositoryProjectsSetting. +""" +type UpdateEnterpriseRepositoryProjectsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated repository projects setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the repository projects setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTeamDiscussionsSetting +""" +input UpdateEnterpriseTeamDiscussionsSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the team discussions setting. + """ + enterpriseId: ID! + + """ + The value for the team discussions setting on the enterprise. + """ + settingValue: EnterpriseEnabledDisabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTeamDiscussionsSetting. +""" +type UpdateEnterpriseTeamDiscussionsSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated team discussions setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the team discussions setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting +""" +input UpdateEnterpriseTwoFactorAuthenticationRequiredSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the enterprise on which to set the two factor authentication required setting. + """ + enterpriseId: ID! + + """ + The value for the two factor authentication required setting on the enterprise. + """ + settingValue: EnterpriseEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateEnterpriseTwoFactorAuthenticationRequiredSetting. +""" +type UpdateEnterpriseTwoFactorAuthenticationRequiredSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The enterprise with the updated two factor authentication required setting. + """ + enterprise: Enterprise + + """ + A message confirming the result of updating the two factor authentication required setting. + """ + message: String +} + +""" +Autogenerated input type of UpdateEnvironment +""" +input UpdateEnvironmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The node ID of the environment. + """ + environmentId: ID! + + """ + The wait timer in minutes. + """ + waitTimer: Int + + """ + The ids of users or teams that can approve deployments to this environment + """ + reviewers: [ID!] + + """ + Whether deployments to this environment can be approved by the user who created the deployment. + """ + preventSelfReview: Boolean +} + +""" +Autogenerated return type of UpdateEnvironment. +""" +type UpdateEnvironmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated environment. + """ + environment: Environment +} + +""" +Autogenerated input type of UpdateIpAllowListEnabledSetting +""" +input UpdateIpAllowListEnabledSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner on which to set the IP allow list enabled setting. + """ + ownerId: ID! + + """ + The value for the IP allow list enabled setting. + """ + settingValue: IpAllowListEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateIpAllowListEnabledSetting. +""" +type UpdateIpAllowListEnabledSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list owner on which the setting was updated. + """ + owner: IpAllowListOwner +} + +""" +Autogenerated input type of UpdateIpAllowListEntry +""" +input UpdateIpAllowListEntryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IP allow list entry to update. + """ + ipAllowListEntryId: ID! + + """ + An IP address or range of addresses in CIDR notation. + """ + allowListValue: String! + + """ + An optional name for the IP allow list entry. + """ + name: String + + """ + Whether the IP allow list entry is active when an IP allow list is enabled. + """ + isActive: Boolean! +} + +""" +Autogenerated return type of UpdateIpAllowListEntry. +""" +type UpdateIpAllowListEntryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list entry that was updated. + """ + ipAllowListEntry: IpAllowListEntry +} + +""" +Autogenerated input type of UpdateIpAllowListForInstalledAppsEnabledSetting +""" +input UpdateIpAllowListForInstalledAppsEnabledSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner. + """ + ownerId: ID! + + """ + The value for the IP allow list configuration for installed GitHub Apps setting. + """ + settingValue: IpAllowListForInstalledAppsEnabledSettingValue! +} + +""" +Autogenerated return type of UpdateIpAllowListForInstalledAppsEnabledSetting. +""" +type UpdateIpAllowListForInstalledAppsEnabledSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IP allow list owner on which the setting was updated. + """ + owner: IpAllowListOwner +} + +""" +Autogenerated input type of UpdateIssueComment +""" +input UpdateIssueCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the IssueComment to modify. + """ + id: ID! + + """ + The updated text of the comment. + """ + body: String! +} + +""" +Autogenerated return type of UpdateIssueComment. +""" +type UpdateIssueCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + issueComment: IssueComment +} + +""" +Autogenerated input type of UpdateIssue +""" +input UpdateIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Issue to modify. + """ + id: ID! + + """ + The title for the issue. + """ + title: String + + """ + The body for the issue description. + """ + body: String + + """ + An array of Node IDs of users for this issue. + """ + assigneeIds: [ID!] + + """ + The Node ID of the milestone for this issue. + """ + milestoneId: ID + + """ + An array of Node IDs of labels for this issue. + """ + labelIds: [ID!] + + """ + The desired issue state. + """ + state: IssueState + + """ + An array of Node IDs for projects associated with this issue. + """ + projectIds: [ID!] +} + +""" +Autogenerated return type of UpdateIssue. +""" +type UpdateIssuePayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The issue. + """ + issue: Issue +} + +""" +Autogenerated input type of UpdateLabel +""" +input UpdateLabelInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the label to be updated. + """ + id: ID! + + """ + A 6 character hex code, without the leading #, identifying the updated color of the label. + """ + color: String + + """ + A brief description of the label, such as its purpose. + """ + description: String + + """ + The updated name of the label. + """ + name: String +} + +""" +Autogenerated return type of UpdateLabel. +""" +type UpdateLabelPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated label. + """ + label: Label +} + +""" +Autogenerated input type of UpdateNotificationRestrictionSetting +""" +input UpdateNotificationRestrictionSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the owner on which to set the restrict notifications setting. + """ + ownerId: ID! + + """ + The value for the restrict notifications setting. + """ + settingValue: NotificationRestrictionSettingValue! +} + +""" +Autogenerated return type of UpdateNotificationRestrictionSetting. +""" +type UpdateNotificationRestrictionSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The owner on which the setting was updated. + """ + owner: VerifiableDomainOwner +} + +""" +Autogenerated input type of UpdateOrganizationAllowPrivateRepositoryForkingSetting +""" +input UpdateOrganizationAllowPrivateRepositoryForkingSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization on which to set the allow private repository forking setting. + """ + organizationId: ID! + + """ + Enable forking of private repositories in the organization? + """ + forkingEnabled: Boolean! +} + +""" +Autogenerated return type of UpdateOrganizationAllowPrivateRepositoryForkingSetting. +""" +type UpdateOrganizationAllowPrivateRepositoryForkingSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the allow private repository forking setting. + """ + message: String + + """ + The organization with the updated allow private repository forking setting. + """ + organization: Organization +} + +""" +Autogenerated input type of UpdateOrganizationWebCommitSignoffSetting +""" +input UpdateOrganizationWebCommitSignoffSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the organization on which to set the web commit signoff setting. + """ + organizationId: ID! + + """ + Enable signoff on web-based commits for repositories in the organization? + """ + webCommitSignoffRequired: Boolean! +} + +""" +Autogenerated return type of UpdateOrganizationWebCommitSignoffSetting. +""" +type UpdateOrganizationWebCommitSignoffSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the web commit signoff setting. + """ + message: String + + """ + The organization with the updated web commit signoff setting. + """ + organization: Organization +} + +""" +Only allow users with bypass permission to update matching refs. +""" +type UpdateParameters { + """ + Branch can pull changes from its upstream repository + """ + updateAllowsFetchAndMerge: Boolean! +} + +""" +Only allow users with bypass permission to update matching refs. +""" +input UpdateParametersInput { + """ + Branch can pull changes from its upstream repository + """ + updateAllowsFetchAndMerge: Boolean! +} + +""" +Autogenerated input type of UpdatePatreonSponsorability +""" +input UpdatePatreonSponsorabilityInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The username of the organization with the GitHub Sponsors profile, if any. Defaults to the GitHub Sponsors profile for the authenticated user if omitted. + """ + sponsorableLogin: String + + """ + Whether Patreon tiers should be shown on the GitHub Sponsors profile page, allowing potential sponsors to make their payment through Patreon instead of GitHub. + """ + enablePatreonSponsorships: Boolean! +} + +""" +Autogenerated return type of UpdatePatreonSponsorability. +""" +type UpdatePatreonSponsorabilityPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The GitHub Sponsors profile. + """ + sponsorsListing: SponsorsListing +} + +""" +Autogenerated input type of UpdateProjectCard +""" +input UpdateProjectCardInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectCard ID to update. + """ + projectCardId: ID! + + """ + Whether or not the ProjectCard should be archived + """ + isArchived: Boolean + + """ + The note of ProjectCard. + """ + note: String +} + +""" +Autogenerated return type of UpdateProjectCard. +""" +type UpdateProjectCardPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated ProjectCard. + """ + projectCard: ProjectCard +} + +""" +Autogenerated input type of UpdateProjectColumn +""" +input UpdateProjectColumnInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ProjectColumn ID to update. + """ + projectColumnId: ID! + + """ + The name of project column. + """ + name: String! +} + +""" +Autogenerated return type of UpdateProjectColumn. +""" +type UpdateProjectColumnPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project column. + """ + projectColumn: ProjectColumn +} + +""" +Autogenerated input type of UpdateProject +""" +input UpdateProjectInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Project ID to update. + """ + projectId: ID! + + """ + The name of project. + """ + name: String + + """ + The description of project. + """ + body: String + + """ + Whether the project is open or closed. + """ + state: ProjectState + + """ + Whether the project is public or not. + """ + public: Boolean +} + +""" +Autogenerated return type of UpdateProject. +""" +type UpdateProjectPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated project. + """ + project: Project +} + +""" +Autogenerated input type of UpdateProjectV2Collaborators +""" +input UpdateProjectV2CollaboratorsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the project to update the collaborators for. + """ + projectId: ID! + + """ + The collaborators to update. + """ + collaborators: [ProjectV2Collaborator!]! +} + +""" +Autogenerated return type of UpdateProjectV2Collaborators. +""" +type UpdateProjectV2CollaboratorsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The collaborators granted a role + """ + collaborators("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2ActorConnection +} + +""" +Autogenerated input type of UpdateProjectV2DraftIssue +""" +input UpdateProjectV2DraftIssueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the draft issue to update. + """ + draftIssueId: ID! + + """ + The title of the draft issue. + """ + title: String + + """ + The body of the draft issue. + """ + body: String + + """ + The IDs of the assignees of the draft issue. + """ + assigneeIds: [ID!] +} + +""" +Autogenerated return type of UpdateProjectV2DraftIssue. +""" +type UpdateProjectV2DraftIssuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The draft issue updated in the project. + """ + draftIssue: DraftIssue +} + +""" +Autogenerated input type of UpdateProjectV2 +""" +input UpdateProjectV2Input { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project to update. + """ + projectId: ID! + + """ + Set the title of the project. + """ + title: String + + """ + Set the short description of the project. + """ + shortDescription: String + + """ + Set the readme description of the project. + """ + readme: String + + """ + Set the project to closed or open. + """ + closed: Boolean + + """ + Set the project to public or private. + """ + public: Boolean +} + +""" +Autogenerated input type of UpdateProjectV2ItemFieldValue +""" +input UpdateProjectV2ItemFieldValueInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project. + """ + projectId: ID! + + """ + The ID of the item to be updated. + """ + itemId: ID! + + """ + The ID of the field to be updated. + """ + fieldId: ID! + + """ + The value which will be set on the field. + """ + value: ProjectV2FieldValue! +} + +""" +Autogenerated return type of UpdateProjectV2ItemFieldValue. +""" +type UpdateProjectV2ItemFieldValuePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated item. + """ + projectV2Item: ProjectV2Item +} + +""" +Autogenerated input type of UpdateProjectV2ItemPosition +""" +input UpdateProjectV2ItemPositionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the Project. + """ + projectId: ID! + + """ + The ID of the item to be moved. + """ + itemId: ID! + + """ + The ID of the item to position this item after. If omitted or set to null the item will be moved to top. + """ + afterId: ID +} + +""" +Autogenerated return type of UpdateProjectV2ItemPosition. +""" +type UpdateProjectV2ItemPositionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The items in the new order + """ + items("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2ItemConnection +} + +""" +Autogenerated return type of UpdateProjectV2. +""" +type UpdateProjectV2Payload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Project. + """ + projectV2: ProjectV2 +} + +""" +Autogenerated input type of UpdateProjectV2StatusUpdate +""" +input UpdateProjectV2StatusUpdateInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the status update to be updated. + """ + statusUpdateId: ID! + + """ + The start date of the status update. + """ + startDate: Date + + """ + The target date of the status update. + """ + targetDate: Date + + """ + The status of the status update. + """ + status: ProjectV2StatusUpdateStatus + + """ + The body of the status update. + """ + body: String +} + +""" +Autogenerated return type of UpdateProjectV2StatusUpdate. +""" +type UpdateProjectV2StatusUpdatePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The status update updated in the project. + """ + statusUpdate: ProjectV2StatusUpdate +} + +""" +Autogenerated input type of UpdatePullRequestBranch +""" +input UpdatePullRequestBranchInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! + + """ + The head ref oid for the upstream branch. + """ + expectedHeadOid: GitObjectID + + """ + The update branch method to use. If omitted, defaults to 'MERGE' + """ + updateMethod: PullRequestBranchUpdateMethod +} + +""" +Autogenerated return type of UpdatePullRequestBranch. +""" +type UpdatePullRequestBranchPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UpdatePullRequest +""" +input UpdatePullRequestInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request. + """ + pullRequestId: ID! + + """ + The name of the branch you want your changes pulled into. This should be an existing branch + on the current repository. + """ + baseRefName: String + + """ + The title of the pull request. + """ + title: String + + """ + The contents of the pull request. + """ + body: String + + """ + The target state of the pull request. + """ + state: PullRequestUpdateState + + """ + Indicates whether maintainers can modify the pull request. + """ + maintainerCanModify: Boolean + + """ + An array of Node IDs of users for this pull request. + """ + assigneeIds: [ID!] + + """ + The Node ID of the milestone for this pull request. + """ + milestoneId: ID + + """ + An array of Node IDs of labels for this pull request. + """ + labelIds: [ID!] + + """ + An array of Node IDs for projects associated with this pull request. + """ + projectIds: [ID!] +} + +""" +Autogenerated return type of UpdatePullRequest. +""" +type UpdatePullRequestPayload { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request. + """ + pullRequest: PullRequest +} + +""" +Autogenerated input type of UpdatePullRequestReviewComment +""" +input UpdatePullRequestReviewCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the comment to modify. + """ + pullRequestReviewCommentId: ID! + + """ + The text of the comment. + """ + body: String! +} + +""" +Autogenerated return type of UpdatePullRequestReviewComment. +""" +type UpdatePullRequestReviewCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + pullRequestReviewComment: PullRequestReviewComment +} + +""" +Autogenerated input type of UpdatePullRequestReview +""" +input UpdatePullRequestReviewInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the pull request review to modify. + """ + pullRequestReviewId: ID! + + """ + The contents of the pull request review body. + """ + body: String! +} + +""" +Autogenerated return type of UpdatePullRequestReview. +""" +type UpdatePullRequestReviewPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated pull request review. + """ + pullRequestReview: PullRequestReview +} + +""" +Autogenerated input type of UpdateRef +""" +input UpdateRefInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the Ref to be updated. + """ + refId: ID! + + """ + The GitObjectID that the Ref shall be updated to target. + """ + oid: GitObjectID! + + """ + Permit updates of branch Refs that are not fast-forwards? + """ + force: Boolean = false +} + +""" +Autogenerated return type of UpdateRef. +""" +type UpdateRefPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated Ref. + """ + ref: Ref +} + +""" +Autogenerated input type of UpdateRefs +""" +input UpdateRefsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + A list of ref updates. + """ + refUpdates: [RefUpdate!]! +} + +""" +Autogenerated return type of UpdateRefs. +""" +type UpdateRefsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated input type of UpdateRepository +""" +input UpdateRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to update. + """ + repositoryId: ID! + + """ + The new name of the repository. + """ + name: String + + """ + A new description for the repository. Pass an empty string to erase the existing description. + """ + description: String + + """ + Whether this repository should be marked as a template such that anyone who can access it can create new repositories with the same files and directory structure. + """ + template: Boolean + + """ + The URL for a web page about this repository. Pass an empty string to erase the existing URL. + """ + homepageUrl: URI + + """ + Indicates if the repository should have the wiki feature enabled. + """ + hasWikiEnabled: Boolean + + """ + Indicates if the repository should have the issues feature enabled. + """ + hasIssuesEnabled: Boolean + + """ + Indicates if the repository should have the project boards feature enabled. + """ + hasProjectsEnabled: Boolean + + """ + Indicates if the repository should have the discussions feature enabled. + """ + hasDiscussionsEnabled: Boolean + + """ + Indicates if the repository displays a Sponsor button for financial contributions. + """ + hasSponsorshipsEnabled: Boolean +} + +""" +Autogenerated return type of UpdateRepository. +""" +type UpdateRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateRepositoryRuleset +""" +input UpdateRepositoryRulesetInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The global relay id of the repository ruleset to be updated. + """ + repositoryRulesetId: ID! + + """ + The name of the ruleset. + """ + name: String + + """ + The target of the ruleset. + """ + target: RepositoryRulesetTarget + + """ + The list of rules for this ruleset + """ + rules: [RepositoryRuleInput!] + + """ + The list of conditions for this ruleset + """ + conditions: RepositoryRuleConditionsInput + + """ + The enforcement level for this ruleset + """ + enforcement: RuleEnforcement + + """ + A list of actors that are allowed to bypass rules in this ruleset. + """ + bypassActors: [RepositoryRulesetBypassActorInput!] +} + +""" +Autogenerated return type of UpdateRepositoryRuleset. +""" +type UpdateRepositoryRulesetPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The newly created Ruleset. + """ + ruleset: RepositoryRuleset +} + +""" +Autogenerated input type of UpdateRepositoryWebCommitSignoffSetting +""" +input UpdateRepositoryWebCommitSignoffSettingInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the repository to update. + """ + repositoryId: ID! + + """ + Indicates if the repository should require signoff on web-based commits. + """ + webCommitSignoffRequired: Boolean! +} + +""" +Autogenerated return type of UpdateRepositoryWebCommitSignoffSetting. +""" +type UpdateRepositoryWebCommitSignoffSettingPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + A message confirming the result of updating the web commit signoff setting. + """ + message: String + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateSponsorshipPreferences +""" +input UpdateSponsorshipPreferencesInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorLogin is not given. + """ + sponsorId: ID + + """ + The username of the user or organization who is acting as the sponsor, paying for the sponsorship. Required if sponsorId is not given. + """ + sponsorLogin: String + + """ + The ID of the user or organization who is receiving the sponsorship. Required if sponsorableLogin is not given. + """ + sponsorableId: ID + + """ + The username of the user or organization who is receiving the sponsorship. Required if sponsorableId is not given. + """ + sponsorableLogin: String + + """ + Whether the sponsor should receive email updates from the sponsorable. + """ + receiveEmails: Boolean = true + + """ + Specify whether others should be able to see that the sponsor is sponsoring the sponsorable. Public visibility still does not reveal which tier is used. + """ + privacyLevel: SponsorshipPrivacy = PUBLIC +} + +""" +Autogenerated return type of UpdateSponsorshipPreferences. +""" +type UpdateSponsorshipPreferencesPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The sponsorship that was updated. + """ + sponsorship: Sponsorship +} + +""" +Autogenerated input type of UpdateSubscription +""" +input UpdateSubscriptionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the subscribable object to modify. + """ + subscribableId: ID! + + """ + The new state of the subscription. + """ + state: SubscriptionState! +} + +""" +Autogenerated return type of UpdateSubscription. +""" +type UpdateSubscriptionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The input subscribable entity. + """ + subscribable: Subscribable +} + +""" +Autogenerated input type of UpdateTeamDiscussionComment +""" +input UpdateTeamDiscussionCommentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the comment to modify. + """ + id: ID! + + """ + The updated text of the comment. + """ + body: String! + + """ + The current version of the body content. + """ + bodyVersion: String +} + +""" +Autogenerated return type of UpdateTeamDiscussionComment. +""" +type UpdateTeamDiscussionCommentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated comment. + """ + teamDiscussionComment: TeamDiscussionComment +} + +""" +Autogenerated input type of UpdateTeamDiscussion +""" +input UpdateTeamDiscussionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the discussion to modify. + """ + id: ID! + + """ + The updated title of the discussion. + """ + title: String + + """ + The updated text of the discussion. + """ + body: String + + """ + The current version of the body content. If provided, this update operation will be rejected if the given version does not match the latest version on the server. + """ + bodyVersion: String + + """ + If provided, sets the pinned state of the updated discussion. + """ + pinned: Boolean +} + +""" +Autogenerated return type of UpdateTeamDiscussion. +""" +type UpdateTeamDiscussionPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The updated discussion. + """ + teamDiscussion: TeamDiscussion +} + +""" +Autogenerated input type of UpdateTeamReviewAssignment +""" +input UpdateTeamReviewAssignmentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the team to update review assignments of + """ + id: ID! + + """ + Turn on or off review assignment + """ + enabled: Boolean! + + """ + The algorithm to use for review assignment + """ + algorithm: TeamReviewAssignmentAlgorithm = ROUND_ROBIN + + """ + The number of team members to assign + """ + teamMemberCount: Int = 1 + + """ + Notify the entire team of the PR if it is delegated + """ + notifyTeam: Boolean = true + + """ + Remove the team review request when assigning + """ + removeTeamRequest: Boolean = true + + """ + Include the members of any child teams when assigning + """ + includeChildTeamMembers: Boolean = true + + """ + Count any members whose review has already been requested against the required number of members assigned to review + """ + countMembersAlreadyRequested: Boolean = true + + """ + An array of team member IDs to exclude + """ + excludedTeamMemberIds: [ID!] +} + +""" +Autogenerated return type of UpdateTeamReviewAssignment. +""" +type UpdateTeamReviewAssignmentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The team that was modified + """ + team: Team +} + +""" +Autogenerated input type of UpdateTeamsRepository +""" +input UpdateTeamsRepositoryInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Repository ID being granted access to. + """ + repositoryId: ID! + + """ + A list of teams being granted access. Limit: 10 + """ + teamIds: [ID!]! + + """ + Permission that should be granted to the teams. + """ + permission: RepositoryPermission! +} + +""" +Autogenerated return type of UpdateTeamsRepository. +""" +type UpdateTeamsRepositoryPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The repository that was updated. + """ + repository: Repository + + """ + The teams granted permission on the repository. + """ + teams: [Team!] +} + +""" +Autogenerated input type of UpdateTopics +""" +input UpdateTopicsInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The Node ID of the repository. + """ + repositoryId: ID! + + """ + An array of topic names. + """ + topicNames: [String!]! +} + +""" +Autogenerated return type of UpdateTopics. +""" +type UpdateTopicsPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Names of the provided topics that are not valid. + """ + invalidTopicNames: [String!] + + """ + The updated repository. + """ + repository: Repository +} + +""" +Autogenerated input type of UpdateUserList +""" +input UpdateUserListInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the list to update. + """ + listId: ID! + + """ + The name of the list + """ + name: String + + """ + A description of the list + """ + description: String + + """ + Whether or not the list is private + """ + isPrivate: Boolean +} + +""" +Autogenerated return type of UpdateUserList. +""" +type UpdateUserListPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The list that was just updated + """ + list: UserList +} + +""" +Autogenerated input type of UpdateUserListsForItem +""" +input UpdateUserListsForItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item to add to the list + """ + itemId: ID! + + """ + The lists to which this item should belong + """ + listIds: [ID!]! + + """ + The suggested lists to create and add this item to + """ + suggestedListIds: [ID!] +} + +""" +Autogenerated return type of UpdateUserListsForItem. +""" +type UpdateUserListsForItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The item that was added + """ + item: UserListItems + + """ + The lists to which this item belongs + """ + lists: [UserList!] + + """ + The user who owns the lists + """ + user: User +} + +""" +A user is an individual's account on GitHub that owns repositories and can make new content. +""" +type User implements Actor & Node & PackageOwner & ProfileOwner & ProjectOwner & ProjectV2Owner & ProjectV2Recent & RepositoryDiscussionAuthor & RepositoryDiscussionCommentAuthor & RepositoryOwner & Sponsorable & UniformResourceLocatable { + """ + Determine if this repository owner has any items that can be pinned to their profile. + """ + anyPinnableItems("Filter to only a particular kind of pinnable item." type: PinnableItemType): Boolean! + + """ + A URL pointing to the user's public avatar. + """ + avatarUrl("The size of the resulting square image." size: Int): URI! + + """ + The user's public profile bio. + """ + bio: String + + """ + The user's public profile bio as HTML. + """ + bioHTML: HTML! + + """ + Could this user receive email notifications, if the organization had notification restrictions enabled? + """ + canReceiveOrganizationEmailsWhenNotificationsRestricted("The login of the organization to check." login: String!): Boolean! + + """ + A list of commit comments made by this user. + """ + commitComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): CommitCommentConnection! + + """ + The user's public profile company. + """ + company: String + + """ + The user's public profile company as HTML. + """ + companyHTML: HTML! + + """ + The collection of contributions this user has made to different repositories. + """ + contributionsCollection("The ID of the organization used to filter contributions." organizationID: ID, "Only contributions made at this time or later will be counted. If omitted, defaults to a year ago." from: DateTime, "Only contributions made before and up to (including) this time will be counted. If omitted, defaults to the current time or one year from the provided from argument." to: DateTime): ContributionsCollection! + + """ + The user's Copilot endpoint information + """ + copilotEndpoints: CopilotEndpoints + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The user's publicly visible profile email. + """ + email: String! + + """ + A list of enterprises that the user belongs to. + """ + enterprises("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the User's enterprises." orderBy: EnterpriseOrder = { + field: NAME + direction: ASC + } + , "Filter enterprises returned based on the user's membership type." membershipType: EnterpriseMembershipType = ALL): EnterpriseConnection + + """ + The estimated next GitHub Sponsors payout for this user/organization in cents (USD). + """ + estimatedNextSponsorsPayoutInCents: Int! + + """ + A list of users the given user is followed by. + """ + followers("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): FollowerConnection! + + """ + A list of users the given user is following. + """ + following("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): FollowingConnection! + + """ + Find gist by repo name. + """ + gist("The gist name to find." name: String!): Gist + + """ + A list of gist comments made by this user. + """ + gistComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): GistCommentConnection! + + """ + A list of the Gists the user has created. + """ + gists("Filters Gists according to privacy." privacy: GistPrivacy, "Ordering options for gists returned from the connection" orderBy: GistOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): GistConnection! + + """ + True if this user/organization has a GitHub Sponsors listing. + """ + hasSponsorsListing: Boolean! + + """ + The hovercard information for this user in a given context + """ + hovercard("The ID of the subject to get the hovercard in the context of" primarySubjectId: ID): Hovercard! + + """ + The Node ID of the User object + """ + id: ID! + + """ + The interaction ability settings for this user. + """ + interactionAbility: RepositoryInteractionAbility + + """ + Whether or not this user is a participant in the GitHub Security Bug Bounty. + """ + isBountyHunter: Boolean! + + """ + Whether or not this user is a participant in the GitHub Campus Experts Program. + """ + isCampusExpert: Boolean! + + """ + Whether or not this user is a GitHub Developer Program member. + """ + isDeveloperProgramMember: Boolean! + + """ + Whether or not this user is a GitHub employee. + """ + isEmployee: Boolean! + + """ + Whether or not this user is following the viewer. Inverse of viewerIsFollowing + """ + isFollowingViewer: Boolean! + + """ + Whether or not this user is a member of the GitHub Stars Program. + """ + isGitHubStar: Boolean! + + """ + Whether or not the user has marked themselves as for hire. + """ + isHireable: Boolean! + + """ + Whether or not this user is a site administrator. + """ + isSiteAdmin: Boolean! + + """ + Whether the given account is sponsoring this user/organization. + """ + isSponsoredBy("The target account's login." accountLogin: String!): Boolean! + + """ + True if the viewer is sponsored by this user/organization. + """ + isSponsoringViewer: Boolean! + + """ + Whether or not this user is the viewing user. + """ + isViewer: Boolean! + + """ + A list of issue comments made by this user. + """ + issueComments("Ordering options for issue comments returned from the connection." orderBy: IssueCommentOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueCommentConnection! + + """ + A list of issues associated with this user. + """ + issues("Ordering options for issues returned from the connection." orderBy: IssueOrder, "A list of label names to filter the pull requests by." labels: [String!], "A list of states to filter the issues by." states: [IssueState!], "Filtering options for issues returned from the connection." filterBy: IssueFilters, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): IssueConnection! + + """ + Showcases a selection of repositories and gists that the profile owner has either curated or that have been selected automatically based on popularity. + """ + itemShowcase: ProfileItemShowcase! + + """ + Calculate how much each sponsor has ever paid total to this maintainer via GitHub Sponsors. Does not include sponsorships paid via Patreon. + """ + lifetimeReceivedSponsorshipValues("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for results returned from the connection." orderBy: SponsorAndLifetimeValueOrder = { + field: SPONSOR_LOGIN + direction: ASC + } + ): SponsorAndLifetimeValueConnection! + + """ + A user-curated list of repositories + """ + lists("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserListConnection! + + """ + The user's public profile location. + """ + location: String + + """ + The username used to login. + """ + login: String! + + """ + The estimated monthly GitHub Sponsors income for this user/organization in cents (USD). + """ + monthlyEstimatedSponsorsIncomeInCents: Int! + + """ + The user's public profile name. + """ + name: String + + """ + Find an organization by its login that the user belongs to. + """ + organization("The login of the organization to find." login: String!): Organization + + """ + Verified email addresses that match verified domains for a specified organization the user is a member of. + """ + organizationVerifiedDomainEmails("The login of the organization to match verified domains from." login: String!): [String!]! + + """ + A list of organizations the user belongs to. + """ + organizations("Ordering options for the User's organizations." orderBy: OrganizationOrder = null, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): OrganizationConnection! + + """ + A list of packages under the owner. + """ + packages("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Find packages by their names." names: [String], "Find packages in a repository by ID." repositoryId: ID, "Filter registry package by type." packageType: PackageType, "Ordering of the returned packages." orderBy: PackageOrder = { + field: CREATED_AT + direction: DESC + } + ): PackageConnection! + + """ + A list of repositories and gists this profile owner can pin to their profile. + """ + pinnableItems("Filter the types of pinnable items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + A list of repositories and gists this profile owner has pinned to their profile + """ + pinnedItems("Filter the types of pinned items that are returned." types: [PinnableItemType!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PinnableItemConnection! + + """ + Returns how many more items this profile owner can pin to their profile. + """ + pinnedItemsRemaining: Int! + + """ + Find project by number. + """ + project("The project number to find." number: Int!): Project + + """ + Find a project by number. + """ + projectV2("The project number." number: Int!): ProjectV2 + + """ + A list of projects under the owner. + """ + projects("Ordering options for projects returned from the connection" orderBy: ProjectOrder, "Query to search projects by, currently only searching by name." search: String, "A list of states to filter the projects by." states: [ProjectState!], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectConnection! + + """ + The HTTP path listing user's projects + """ + projectsResourcePath: URI! + + """ + The HTTP URL listing user's projects + """ + projectsUrl: URI! + + """ + A list of projects under the owner. + """ + projectsV2("A project to search for under the the owner." query: String, "How to order the returned projects." orderBy: ProjectV2Order = { + field: NUMBER + direction: DESC + } + , "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + The user's profile pronouns + """ + pronouns: String + + """ + A list of public keys associated with this user. + """ + publicKeys("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PublicKeyConnection! + + """ + A list of pull requests associated with this user. + """ + pullRequests("A list of states to filter the pull requests by." states: [PullRequestState!], "A list of label names to filter the pull requests by." labels: [String!], "The head ref name to filter the pull requests by." headRefName: String, "The base ref name to filter the pull requests by." baseRefName: String, "Ordering options for pull requests returned from the connection." orderBy: IssueOrder, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): PullRequestConnection! + + """ + Recent projects that this user has modified in the context of the owner. + """ + recentProjects("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): ProjectV2Connection! + + """ + A list of repositories that the user owns. + """ + repositories("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Array of viewer's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the current viewer owns." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If non-null, filters repositories according to whether they are archived and not maintained" isArchived: Boolean, "If non-null, filters repositories according to whether they are forks of another repository" isFork: Boolean): RepositoryConnection! + + """ + A list of repositories that the user recently contributed to. + """ + repositoriesContributedTo("If non-null, filters repositories according to privacy" privacy: RepositoryPrivacy, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssues: Boolean, "If true, include user repositories" includeUserRepositories: Boolean, "If non-null, include only the specified types of contributions. The GitHub.com UI uses [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]" contributionTypes: [RepositoryContributionType], "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryConnection! + + """ + Find Repository. + """ + repository("Name of Repository to find." name: String!, "Follow repository renames. If disabled, a repository referenced by its old name will return an error." followRenames: Boolean = true): Repository + + """ + Discussion comments this user has authored. + """ + repositoryDiscussionComments("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter discussion comments to only those in a specific repository." repositoryId: ID, "Filter discussion comments to only those that were marked as the answer" onlyAnswers: Boolean = false): DiscussionCommentConnection! + + """ + Discussions this user has started. + """ + repositoryDiscussions("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for discussions returned from the connection." orderBy: DiscussionOrder = { + field: CREATED_AT + direction: DESC + } + , "Filter discussions to only those in a specific repository." repositoryId: ID, "Filter discussions to only those that have been answered or not. Defaults to including both answered and unanswered discussions." answered: Boolean = null, "A list of states to filter the discussions by." states: [DiscussionState!] = []): DiscussionConnection! + + """ + The HTTP path for this user + """ + resourcePath: URI! + + """ + Replies this user has saved + """ + savedReplies("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "The field to order saved replies by." orderBy: SavedReplyOrder = { + field: UPDATED_AT + direction: DESC + } + ): SavedReplyConnection + + """ + The user's social media accounts, ordered as they appear on the user's profile. + """ + socialAccounts("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): SocialAccountConnection! + + """ + List of users and organizations this entity is sponsoring. + """ + sponsoring("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the users and organizations returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + List of sponsors for this user or organization. + """ + sponsors("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "If given, will filter for sponsors at the given tier. Will only return sponsors whose tier the viewer is permitted to see." tierId: ID, "Ordering options for sponsors returned from the connection." orderBy: SponsorOrder = { + field: RELEVANCE + direction: DESC + } + ): SponsorConnection! + + """ + Events involving this sponsorable, such as new sponsorships. + """ + sponsorsActivities("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filter activities returned to only those that occurred in the most recent specified time period. Set to ALL to avoid filtering by when the activity occurred. Will be ignored if `since` or `until` is given." period: SponsorsActivityPeriod = MONTH, "Filter activities to those that occurred on or after this time." since: DateTime, "Filter activities to those that occurred before this time." until: DateTime, "Ordering options for activity returned from the connection." orderBy: SponsorsActivityOrder = { + field: TIMESTAMP + direction: DESC + } + , "Filter activities to only the specified actions." actions: [SponsorsActivityAction!] = [], "Whether to include those events where this sponsorable acted as the sponsor. Defaults to only including events where this sponsorable was the recipient of a sponsorship." includeAsSponsor: Boolean = false, "Whether or not to include private activities in the result set. Defaults to including public and private activities." includePrivate: Boolean = true): SponsorsActivityConnection! + + """ + The GitHub Sponsors listing for this user or organization. + """ + sponsorsListing: SponsorsListing + + """ + The sponsorship from the viewer to this user/organization; that is, the sponsorship where you're the sponsor. + """ + sponsorshipForViewerAsSponsor("Whether to return the sponsorship only if it's still active. Pass false to get the viewer's sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + The sponsorship from this user/organization to the viewer; that is, the sponsorship you're receiving. + """ + sponsorshipForViewerAsSponsorable("Whether to return the sponsorship only if it's still active. Pass false to get the sponsorship back even if it has been cancelled." activeOnly: Boolean = true): Sponsorship + + """ + List of sponsorship updates sent from this sponsorable to sponsors. + """ + sponsorshipNewsletters("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorship updates returned from the connection." orderBy: SponsorshipNewsletterOrder = { + field: CREATED_AT + direction: DESC + } + ): SponsorshipNewsletterConnection! + + """ + The sponsorships where this user or organization is the maintainer receiving the funds. + """ + sponsorshipsAsMaintainer("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Whether or not to include private sponsorships in the result set" includePrivate: Boolean = false, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Whether to include only sponsorships that are active right now, versus all sponsorships this maintainer has ever received." activeOnly: Boolean = true): SponsorshipConnection! + + """ + The sponsorships where this user or organization is the funder. + """ + sponsorshipsAsSponsor("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for sponsorships returned from this connection. If left blank, the sponsorships will be ordered based on relevancy to the viewer." orderBy: SponsorshipOrder, "Filter sponsorships returned to those for the specified maintainers. That is, the recipient of the sponsorship is a user or organization with one of the given logins." maintainerLogins: [String!], "Whether to include only sponsorships that are active right now, versus all sponsorships this sponsor has ever made." activeOnly: Boolean = true): SponsorshipConnection! + + """ + Repositories the user has starred. + """ + starredRepositories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Filters starred repositories to only return repositories owned by the viewer." ownedByViewer: Boolean, "Order for connection" orderBy: StarOrder): StarredRepositoryConnection! + + """ + The user's description of what they're currently doing. + """ + status: UserStatus + + """ + Suggested names for user lists + """ + suggestedListNames: [UserListSuggestion!]! + + """ + Repositories the user has contributed to, ordered by contribution rank, plus repositories the user has created + """ + topRepositories("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder!, "How far back in time to fetch contributed repositories" since: DateTime): RepositoryConnection! + + """ + The amount in United States cents (e.g., 500 = $5.00 USD) that this entity has spent on GitHub to fund sponsorships. Only returns a value when viewed by the user themselves or by a user who can manage sponsorships for the requested organization. + """ + totalSponsorshipAmountAsSponsorInCents("Filter payments to those that occurred on or after this time." since: DateTime, "Filter payments to those that occurred before this time." until: DateTime, "Filter payments to those made to the users or organizations with the specified usernames." sponsorableLogins: [String!] = []): Int + + """ + The user's Twitter username. + """ + twitterUsername: String + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this user + """ + url: URI! + + """ + Verified domains available to this organization. + """ + verifiedDomainsList: [String!]! + + """ + Can the viewer pin repositories and gists to the profile? + """ + viewerCanChangePinnedItems: Boolean! + + """ + Can the current viewer create new projects on this owner. + """ + viewerCanCreateProjects: Boolean! + + """ + Whether or not the viewer is able to follow the user. + """ + viewerCanFollow: Boolean! + + """ + Whether or not the viewer is able to sponsor this user/organization. + """ + viewerCanSponsor: Boolean! + + """ + Whether or not this user is followed by the viewer. Inverse of isFollowingViewer. + """ + viewerIsFollowing: Boolean! + + """ + True if the viewer is sponsoring this user/organization. + """ + viewerIsSponsoring: Boolean! + + """ + A list of repositories the given user is watching. + """ + watching("If non-null, filters repositories according to privacy. Internal repositories are considered private; consider using the visibility argument if only internal repositories are needed. Cannot be combined with the visibility argument." privacy: RepositoryPrivacy, "If non-null, filters repositories according to visibility. Cannot be combined with the privacy argument." visibility: RepositoryVisibility, "Ordering options for repositories returned from the connection" orderBy: RepositoryOrder, "Affiliation options for repositories returned from the connection. If none specified, the results will include repositories for which the current viewer is an owner or collaborator, or member." affiliations: [RepositoryAffiliation], "Array of owner's affiliation options for repositories returned from the connection. For example, OWNER will include only repositories that the organization or user being viewed owns." ownerAffiliations: [RepositoryAffiliation] = [OWNER,COLLABORATOR], "If non-null, filters repositories according to whether they have been locked" isLocked: Boolean, "If non-null, filters repositories according to whether they have issues enabled" hasIssuesEnabled: Boolean, "Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): RepositoryConnection! + + """ + A URL pointing to the user's public website/blog. + """ + websiteUrl: URI +} + +""" +The possible durations that a user can be blocked for. +""" +enum UserBlockDuration { + """ + The user was blocked for 1 day + """ + ONE_DAY + + """ + The user was blocked for 3 days + """ + THREE_DAYS + + """ + The user was blocked for 7 days + """ + ONE_WEEK + + """ + The user was blocked for 30 days + """ + ONE_MONTH + + """ + The user was blocked permanently + """ + PERMANENT +} + +""" +Represents a 'user_blocked' event on a given user. +""" +type UserBlockedEvent implements Node { + """ + Identifies the actor who performed the event. + """ + actor: Actor + + """ + Number of days that the user was blocked for. + """ + blockDuration: UserBlockDuration! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The Node ID of the UserBlockedEvent object + """ + id: ID! + + """ + The user who was blocked. + """ + subject: User +} + +""" +A list of users. +""" +type UserConnection { + """ + A list of edges. + """ + edges: [UserEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edit on user content +""" +type UserContentEdit implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the date and time when the object was deleted. + """ + deletedAt: DateTime + + """ + The actor who deleted this content + """ + deletedBy: Actor + + """ + A summary of the changes for this edit + """ + diff: String + + """ + When this content was edited + """ + editedAt: DateTime! + + """ + The actor who edited this content + """ + editor: Actor + + """ + The Node ID of the UserContentEdit object + """ + id: ID! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! +} + +""" +A list of edits to content. +""" +type UserContentEditConnection { + """ + A list of edges. + """ + edges: [UserContentEditEdge] + + """ + A list of nodes. + """ + nodes: [UserContentEdit] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserContentEditEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserContentEdit +} + +""" +Represents a user. +""" +type UserEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: User +} + +""" +Email attributes from External Identity +""" +type UserEmailMetadata { + """ + Boolean to identify primary emails + """ + primary: Boolean + + """ + Type of email + """ + type: String + + """ + Email id + """ + value: String! +} + +""" +A user-curated list of repositories +""" +type UserList implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + The description of this list + """ + description: String + + """ + The Node ID of the UserList object + """ + id: ID! + + """ + Whether or not this list is private + """ + isPrivate: Boolean! + + """ + The items associated with this list + """ + items("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): UserListItemsConnection! + + """ + The date and time at which this list was created or last had items added to it + """ + lastAddedAt: DateTime! + + """ + The name of this list + """ + name: String! + + """ + The slug of this list + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user to which this list belongs + """ + user: User! +} + +""" +The connection type for UserList. +""" +type UserListConnection { + """ + A list of edges. + """ + edges: [UserListEdge] + + """ + A list of nodes. + """ + nodes: [UserList] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserListEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserList +} + +""" +Types that can be added to a user list. +""" +union UserListItems = Repository + +""" +The connection type for UserListItems. +""" +type UserListItemsConnection { + """ + A list of edges. + """ + edges: [UserListItemsEdge] + + """ + A list of nodes. + """ + nodes: [UserListItems] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserListItemsEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserListItems +} + +""" +Represents a suggested user list. +""" +type UserListSuggestion { + """ + The ID of the suggested user list + """ + id: ID + + """ + The name of the suggested user list + """ + name: String +} + +""" +The user's description of what they're currently doing. +""" +type UserStatus implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + An emoji summarizing the user's status. + """ + emoji: String + + """ + The status emoji as HTML. + """ + emojiHTML: HTML + + """ + If set, the status will not be shown after this date. + """ + expiresAt: DateTime + + """ + The Node ID of the UserStatus object + """ + id: ID! + + """ + Whether this status indicates the user is not fully available on GitHub. + """ + indicatesLimitedAvailability: Boolean! + + """ + A brief message describing what the user is doing. + """ + message: String + + """ + The organization whose members can see this status. If null, this status is publicly visible. + """ + organization: Organization + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The user who has this status. + """ + user: User! +} + +""" +The connection type for UserStatus. +""" +type UserStatusConnection { + """ + A list of edges. + """ + edges: [UserStatusEdge] + + """ + A list of nodes. + """ + nodes: [UserStatus] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type UserStatusEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: UserStatus +} + +""" +Ordering options for user status connections. +""" +input UserStatusOrder { + """ + The field to order user statuses by. + """ + field: UserStatusOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which user status connections can be ordered. +""" +enum UserStatusOrderField { + """ + Order user statuses by when they were updated. + """ + UPDATED_AT +} + +""" +A domain that can be verified or approved for an organization or an enterprise. +""" +type VerifiableDomain implements Node { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The DNS host name that should be used for verification. + """ + dnsHostName: URI + + """ + The unicode encoded domain. + """ + domain: URI! + + """ + Whether a TXT record for verification with the expected host name was found. + """ + hasFoundHostName: Boolean! + + """ + Whether a TXT record for verification with the expected verification token was found. + """ + hasFoundVerificationToken: Boolean! + + """ + The Node ID of the VerifiableDomain object + """ + id: ID! + + """ + Whether or not the domain is approved. + """ + isApproved: Boolean! + + """ + Whether this domain is required to exist for an organization or enterprise policy to be enforced. + """ + isRequiredForPolicyEnforcement: Boolean! + + """ + Whether or not the domain is verified. + """ + isVerified: Boolean! + + """ + The owner of the domain. + """ + owner: VerifiableDomainOwner! + + """ + The punycode encoded domain. + """ + punycodeEncodedDomain: URI! + + """ + The time that the current verification token will expire. + """ + tokenExpirationTime: DateTime + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The current verification token for the domain. + """ + verificationToken: String +} + +""" +The connection type for VerifiableDomain. +""" +type VerifiableDomainConnection { + """ + A list of edges. + """ + edges: [VerifiableDomainEdge] + + """ + A list of nodes. + """ + nodes: [VerifiableDomain] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type VerifiableDomainEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: VerifiableDomain +} + +""" +Ordering options for verifiable domain connections. +""" +input VerifiableDomainOrder { + """ + The field to order verifiable domains by. + """ + field: VerifiableDomainOrderField! + + """ + The ordering direction. + """ + direction: OrderDirection! +} + +""" +Properties by which verifiable domain connections can be ordered. +""" +enum VerifiableDomainOrderField { + """ + Order verifiable domains by the domain name. + """ + DOMAIN + + """ + Order verifiable domains by their creation date. + """ + CREATED_AT +} + +""" +Types that can own a verifiable domain. +""" +union VerifiableDomainOwner = Enterprise|Organization + +""" +Autogenerated input type of VerifyVerifiableDomain +""" +input VerifyVerifiableDomainInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The ID of the verifiable domain to verify. + """ + id: ID! +} + +""" +Autogenerated return type of VerifyVerifiableDomain. +""" +type VerifyVerifiableDomainPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The verifiable domain that was verified. + """ + domain: VerifiableDomain +} + +""" +A hovercard context with a message describing how the viewer is related. +""" +type ViewerHovercardContext implements HovercardContext { + """ + A string describing this context + """ + message: String! + + """ + An octicon to accompany this context + """ + octicon: String! + + """ + Identifies the user who is related to this context. + """ + viewer: User! +} + +""" +A subject that may be upvoted. +""" +interface Votable { + """ + Number of upvotes that this subject has received. + """ + upvoteCount: Int! + + """ + Whether or not the current user can add or remove an upvote on this subject. + """ + viewerCanUpvote: Boolean! + + """ + Whether or not the current user has already upvoted this subject. + """ + viewerHasUpvoted: Boolean! +} + +""" +A workflow contains meta information about an Actions workflow file. +""" +type Workflow implements Node & UniformResourceLocatable { + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The Node ID of the Workflow object + """ + id: ID! + + """ + The name of the workflow. + """ + name: String! + + """ + The HTTP path for this workflow + """ + resourcePath: URI! + + """ + The runs of the workflow. + """ + runs("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int, "Ordering options for the connection" orderBy: WorkflowRunOrder = { + field: CREATED_AT + direction: DESC + } + ): WorkflowRunConnection! + + """ + The state of the workflow. + """ + state: WorkflowState! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this workflow + """ + url: URI! +} + +""" +A workflow that must run for this rule to pass +""" +type WorkflowFileReference { + """ + The path to the workflow file + """ + path: String! + + """ + The ref (branch or tag) of the workflow file to use + """ + ref: String + + """ + The ID of the repository where the workflow is defined + """ + repositoryId: Int! + + """ + The commit SHA of the workflow file to use + """ + sha: String +} + +""" +A workflow that must run for this rule to pass +""" +input WorkflowFileReferenceInput { + """ + The path to the workflow file + """ + path: String! + + """ + The ref (branch or tag) of the workflow file to use + """ + ref: String + + """ + The ID of the repository where the workflow is defined + """ + repositoryId: Int! + + """ + The commit SHA of the workflow file to use + """ + sha: String +} + +""" +A workflow run. +""" +type WorkflowRun implements Node & UniformResourceLocatable { + """ + The check suite this workflow run belongs to. + """ + checkSuite: CheckSuite! + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The log of deployment reviews + """ + deploymentReviews("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentReviewConnection! + + """ + The event that triggered the workflow run + """ + event: String! + + """ + The workflow file + """ + file: WorkflowRunFile + + """ + The Node ID of the WorkflowRun object + """ + id: ID! + + """ + The pending deployment requests of all check runs in this workflow run + """ + pendingDeploymentRequests("Returns the elements in the list that come after the specified cursor." after: String, "Returns the elements in the list that come before the specified cursor." before: String, "Returns the first _n_ elements from the list." first: Int, "Returns the last _n_ elements from the list." last: Int): DeploymentRequestConnection! + + """ + The HTTP path for this workflow run + """ + resourcePath: URI! + + """ + A number that uniquely identifies this workflow run in its parent workflow. + """ + runNumber: Int! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this workflow run + """ + url: URI! + + """ + The workflow executed in this workflow run. + """ + workflow: Workflow! +} + +""" +The connection type for WorkflowRun. +""" +type WorkflowRunConnection { + """ + A list of edges. + """ + edges: [WorkflowRunEdge] + + """ + A list of nodes. + """ + nodes: [WorkflowRun] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type WorkflowRunEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: WorkflowRun +} + +""" +An executed workflow file for a workflow run. +""" +type WorkflowRunFile implements Node & UniformResourceLocatable { + """ + The Node ID of the WorkflowRunFile object + """ + id: ID! + + """ + The path of the workflow file relative to its repository. + """ + path: String! + + """ + The direct link to the file in the repository which stores the workflow file. + """ + repositoryFileUrl: URI! + + """ + The repository name and owner which stores the workflow file. + """ + repositoryName: URI! + + """ + The HTTP path for this workflow run file + """ + resourcePath: URI! + + """ + The parent workflow run execution for this file. + """ + run: WorkflowRun! + + """ + The HTTP URL for this workflow run file + """ + url: URI! + + """ + If the viewer has permissions to push to the repository which stores the workflow. + """ + viewerCanPushRepository: Boolean! + + """ + If the viewer has permissions to read the repository which stores the workflow. + """ + viewerCanReadRepository: Boolean! +} + +""" +Ways in which lists of workflow runs can be ordered upon return. +""" +input WorkflowRunOrder { + """ + The field by which to order workflows. + """ + field: WorkflowRunOrderField! + + """ + The direction in which to order workflow runs by the specified field. + """ + direction: OrderDirection! +} + +""" +Properties by which workflow run connections can be ordered. +""" +enum WorkflowRunOrderField { + """ + Order workflow runs by most recently created + """ + CREATED_AT +} + +""" +The possible states for a workflow. +""" +enum WorkflowState { + """ + The workflow is active. + """ + ACTIVE + + """ + The workflow was deleted from the git repository. + """ + DELETED + + """ + The workflow was disabled by default on a fork. + """ + DISABLED_FORK + + """ + The workflow was disabled for inactivity in the repository. + """ + DISABLED_INACTIVITY + + """ + The workflow was disabled manually. + """ + DISABLED_MANUALLY +} + +""" +Require all changes made to a targeted branch to pass the specified workflows before they can be merged. +""" +type WorkflowsParameters { + """ + Workflows that must pass for this rule to pass. + """ + workflows: [WorkflowFileReference!]! +} + +""" +Require all changes made to a targeted branch to pass the specified workflows before they can be merged. +""" +input WorkflowsParametersInput { + """ + Workflows that must pass for this rule to pass. + """ + workflows: [WorkflowFileReferenceInput!]! +} + +""" +A valid x509 certificate string +""" +scalar X509Certificate + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. + +In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. +""" +type __Directive { + args(includeDeprecated: Boolean = false): [__InputValue!]! + + description: String + + isRepeatable: Boolean + + locations: [__DirectiveLocation!]! + + name: String! + + onField: Boolean! @deprecated(reason: "Use `locations`.") + + onFragment: Boolean! @deprecated(reason: "Use `locations`.") + + onOperation: Boolean! @deprecated(reason: "Use `locations`.") +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. +""" +enum __DirectiveLocation { + """ + Location adjacent to a query operation. + """ + QUERY + + """ + Location adjacent to a mutation operation. + """ + MUTATION + + """ + Location adjacent to a subscription operation. + """ + SUBSCRIPTION + + """ + Location adjacent to a field. + """ + FIELD + + """ + Location adjacent to a fragment definition. + """ + FRAGMENT_DEFINITION + + """ + Location adjacent to a fragment spread. + """ + FRAGMENT_SPREAD + + """ + Location adjacent to an inline fragment. + """ + INLINE_FRAGMENT + + """ + Location adjacent to a schema definition. + """ + SCHEMA + + """ + Location adjacent to a scalar definition. + """ + SCALAR + + """ + Location adjacent to an object type definition. + """ + OBJECT + + """ + Location adjacent to a field definition. + """ + FIELD_DEFINITION + + """ + Location adjacent to an argument definition. + """ + ARGUMENT_DEFINITION + + """ + Location adjacent to an interface definition. + """ + INTERFACE + + """ + Location adjacent to a union definition. + """ + UNION + + """ + Location adjacent to an enum definition. + """ + ENUM + + """ + Location adjacent to an enum value definition. + """ + ENUM_VALUE + + """ + Location adjacent to an input object type definition. + """ + INPUT_OBJECT + + """ + Location adjacent to an input object field definition. + """ + INPUT_FIELD_DEFINITION + + """ + Location adjacent to a variable definition. + """ + VARIABLE_DEFINITION +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. +""" +type __EnumValue { + deprecationReason: String + + description: String + + isDeprecated: Boolean! + + name: String! +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type. +""" +type __Field { + args(includeDeprecated: Boolean = false): [__InputValue!]! + + deprecationReason: String + + description: String + + isDeprecated: Boolean! + + name: String! + + type: __Type! +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. +""" +type __InputValue { + """ + A GraphQL-formatted string representing the default value for this input value. + """ + defaultValue: String + + deprecationReason: String + + description: String + + isDeprecated: Boolean! + + name: String! + + type: __Type! +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. +""" +type __Schema { + description: String + + """ + A list of all directives supported by this server. + """ + directives: [__Directive!]! + + """ + If this server supports mutation, the type that mutation operations will be rooted at. + """ + mutationType: __Type + + """ + The type that query operations will be rooted at. + """ + queryType: __Type! + + """ + If this server support subscription, the type that subscription operations will be rooted at. + """ + subscriptionType: __Type + + """ + A list of all types supported by this server. + """ + types: [__Type!]! +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. + +Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. +""" +type __Type { + description: String + + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + + fields(includeDeprecated: Boolean = false): [__Field!] + + inputFields(includeDeprecated: Boolean = false): [__InputValue!] + + interfaces: [__Type!] + + isOneOf: Boolean! + + kind: __TypeKind! + + name: String + + ofType: __Type + + possibleTypes: [__Type!] + + specifiedByURL: String +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +An enum describing what kind of type a given `__Type` is. +""" +enum __TypeKind { + """ + Indicates this type is a scalar. + """ + SCALAR + + """ + Indicates this type is an object. `fields` and `interfaces` are valid fields. + """ + OBJECT + + """ + Indicates this type is an interface. `fields` and `possibleTypes` are valid fields. + """ + INTERFACE + + """ + Indicates this type is a union. `possibleTypes` is a valid field. + """ + UNION + + """ + Indicates this type is an enum. `enumValues` is a valid field. + """ + ENUM + + """ + Indicates this type is an input object. `inputFields` is a valid field. + """ + INPUT_OBJECT + + """ + Indicates this type is a list. `ofType` is a valid field. + """ + LIST + + """ + Indicates this type is a non-null. `ofType` is a valid field. + """ + NON_NULL +} + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +Directs the executor to include this field or fragment only when the `if` argument is true. +""" +directive @include ("Included when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +Directs the executor to skip this field or fragment when the `if` argument is true. +""" +directive @skip ("Skipped when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT + +# See https://github.com/JetBrains/js-graphql-intellij-plugin/issues/665 +# noinspection GraphQLTypeRedefinition +""" +Marks an element of a GraphQL schema as no longer supported. +""" +directive @deprecated ("Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https:\/\/daringfireball.net\/projects\/markdown\/)." reason: String = "No longer supported") on FIELD_DEFINITION|ENUM_VALUE|ARGUMENT_DEFINITION|INPUT_FIELD_DEFINITION + +""" +Requires that exactly one field must be supplied and that field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + +directive @requiredCapabilities (requiredCapabilities: [String!]) on OBJECT|SCALAR|ARGUMENT_DEFINITION|INTERFACE|INPUT_OBJECT|FIELD_DEFINITION|ENUM|ENUM_VALUE|UNION|INPUT_FIELD_DEFINITION + +""" +Directs the executor to deliver this part of the result after the initial result. +""" +directive @defer ("If false, this field _won't_ be deferred." if: Boolean = true, "A unique label to identify the deferred payload." label: String) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT + +schema { + query: Query + mutation: Mutation +} diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/GitHub.kt b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/GitHub.kt similarity index 58% rename from network/src/commonMain/kotlin/dev/datlag/burningseries/network/GitHub.kt rename to github/src/commonMain/kotlin/dev/datlag/burningseries/github/GitHub.kt index b160042e..7957f12f 100644 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/GitHub.kt +++ b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/GitHub.kt @@ -1,16 +1,16 @@ -package dev.datlag.burningseries.network +package dev.datlag.burningseries.github import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.Headers import de.jensklingenberg.ktorfit.http.Path -import dev.datlag.burningseries.model.Release +import dev.datlag.burningseries.github.model.RESTRelease interface GitHub { @Headers("Accept: application/vnd.github+json") - @GET("repos/{owner}/{repo}/releases") - suspend fun getReleases( + @GET("repos/{owner}/{repo}/releases/latest") + suspend fun getLatestRelease( @Path("owner") owner: String, @Path("repo") repo: String - ): List + ): RESTRelease } \ No newline at end of file diff --git a/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/Asset.kt b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/Asset.kt new file mode 100644 index 00000000..29a4f59f --- /dev/null +++ b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/Asset.kt @@ -0,0 +1,43 @@ +package dev.datlag.burningseries.github.model + +import dev.datlag.burningseries.github.UserAndReleaseQuery +import dev.datlag.burningseries.model.common.toInt +import io.ktor.http.ContentType +import io.ktor.http.Url +import io.ktor.http.fromFilePath +import io.ktor.http.fullPath +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class Asset( + @SerialName("name") val name: String, + @SerialName("content_type") val contentType: String? = null, + @SerialName("browser_download_url") private val _downloadUrl: String +) { + + @Transient + val downloadUrl: Url = Url(_downloadUrl) + + @Transient + val isApkContentType: Boolean = contentType.equals("application/vnd.android.package-archive", ignoreCase = true) + + @Transient + val hasApkNameEnding: Boolean = name.endsWith(".apk") + + @Transient + val hasApkUrlEnding: Boolean = downloadUrl.encodedPath.endsWith(".apk") + + @Transient + val apkIdentifier: Int = isApkContentType.toInt() + hasApkNameEnding.toInt() + hasApkUrlEnding.toInt() + + @Transient + val hasAnyApkIdentifier = apkIdentifier > 0 + + constructor(node: UserAndReleaseQuery.Node) : this( + name = node.name, + contentType = node.contentType.ifBlank { null }, + _downloadUrl = (node.downloadUrl as? CharSequence)?.toString()?.ifBlank { null } ?: node.downloadUrl.toString() + ) +} diff --git a/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/RESTRelease.kt b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/RESTRelease.kt new file mode 100644 index 00000000..659a645b --- /dev/null +++ b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/RESTRelease.kt @@ -0,0 +1,16 @@ +package dev.datlag.burningseries.github.model + +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RESTRelease( + @SerialName("html_url") val htmlUrl: String, + @SerialName("tag_name") val tagName: String, + @SerialName("name") val title: String, + @SerialName("draft") val draft: Boolean = false, + @SerialName("prerelease") val preRelease: Boolean = false, + @SerialName("assets") val assets: SerializableImmutableSet = persistentSetOf() +) diff --git a/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/UserAndRelease.kt b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/UserAndRelease.kt new file mode 100644 index 00000000..c5426bac --- /dev/null +++ b/github/src/commonMain/kotlin/dev/datlag/burningseries/github/model/UserAndRelease.kt @@ -0,0 +1,102 @@ +package dev.datlag.burningseries.github.model + +import dev.datlag.burningseries.github.UserAndReleaseQuery +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import dev.datlag.tooling.getDigitsOrNull +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class UserAndRelease( + val user: User?, + val release: Release? +) { + + constructor(info: UserAndReleaseQuery.Data) : this( + user = User(info), + release = info.repository?.latestRelease?.let(UserAndRelease::Release) + ) + + constructor(release: RESTRelease) : this( + user = null, + release = Release(release) + ) + + @Serializable + data class User( + val isSponsoring: Boolean, + val hasStarred: Boolean, + val avatar: String?, + private val _name: String?, + val login: String + ) { + + @Transient + val name: String = _name?.ifBlank { null } ?: login + + constructor(info: UserAndReleaseQuery.Data) : this( + isSponsoring = info.user?.viewerIsSponsoring == true || info.user?.isViewer == true, + hasStarred = info.repository?.viewerHasStarred == true, + avatar = (info.viewer.avatarUrl as? CharSequence)?.toString()?.ifBlank { null }, + _name = info.viewer.name?.ifBlank { null }, + login = info.viewer.login + ) + } + + @Serializable + data class Release( + val url: String?, + val tagName: String, + val title: String?, + val isPrerelease: Boolean, + val isDraft: Boolean, + val assets: SerializableImmutableSet = persistentSetOf() + ) { + + @Transient + val tagNumber = tagName.getDigitsOrNull()?.toIntOrNull() + + @Transient + val androidAsset = assets.maxByOrNull { it.apkIdentifier }?.let { a -> + if (a.hasAnyApkIdentifier) { + a + } else { + null + } + } + + constructor(release: UserAndReleaseQuery.LatestRelease) : this( + url = (release.url as? CharSequence)?.toString()?.ifBlank { null }, + tagName = release.tagName, + title = release.name?.ifBlank { null }, + isPrerelease = release.isPrerelease, + isDraft = release.isDraft, + assets = release.releaseAssets.nodesFilterNotNull()?.map(::Asset).orEmpty().toImmutableSet() + ) + + constructor(release: RESTRelease) : this( + url = release.htmlUrl, + tagName = release.tagName, + title = release.title, + isPrerelease = release.preRelease, + isDraft = release.draft, + assets = release.assets + ) + + fun asUpdateOrNull(currentVersion: String?): Release? { + if (currentVersion.isNullOrBlank()) { + return null + } + val releaseNumber = tagNumber ?: return null + val currentNumber = currentVersion.getDigitsOrNull()?.toIntOrNull() ?: return null + + if (releaseNumber > currentNumber) { + return this + } + return null + } + } +} diff --git a/gradle.properties b/gradle.properties index 79eece2e..de17d5eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,21 +1,26 @@ #Gradle -org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M" +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.configureondemand=true +# org.gradle.configuration-cache=true #Kotlin kotlin.code.style=official +kotlin.js.compiler=ir -#MPP -kotlin.mpp.stability.nowarn=true -kotlin.mpp.enableCInteropCommonization=true -kotlin.mpp.androidSourceSetLayoutVersion=2 +#Android +android.nonTransitiveRClass=true +android.useAndroidX=true #Compose org.jetbrains.compose.experimental.uikit.enabled=true +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true -android.useAndroidX=true - -org.gradle.daemon=true -org.gradle.configureondemand=true +#MPP +kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlin.mpp.enableCInteropCommonization=true systemProp.javax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl systemProp.javax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97003232..4bf244cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,122 +1,112 @@ [versions] -app = "5.5.1" -aboutlibraries = "11.1.4" -accompanist = "0.34.0" +app = "6.0.0" +aboutlibraries = "11.2.1" +ackpine = "0.6.1" activity = "1.9.0" -android = "8.2.2" +android = "8.4.1" android-core = "1.13.1" android-sqlite = "2.4.0" -appcompat = "1.6.1" -appdirs = "1.2.2" +apollo = "4.0.0-beta.6" +atomicfu = "0.24.0" blurhash = "0.3.0" coil = "3.0.0-alpha06" -compose = "1.6.2" -complete-kotlin = "1.1.0" -context-menu = "0.2.0" +compose = "1.6.11" coroutines = "1.8.1" -crashlytics-plugin = "3.0.1" +crashlytics-plugin = "3.0.2" datastore = "1.1.1" -datetime = "0.5.0" -decompose = "3.0.0" +datetime = "0.6.0" +decompose = "3.1.0" +desugar = "2.0.4" firebase = "1.12.0" firebase-android = "21.0.0" +firebase-android-analytics = "22.0.1" firebase-android-auth = "23.0.0" -firebase-android-crashlytics = "19.0.0" -firebase-android-firestore = "25.0.0" -flowredux = "1.2.1" -grpc = "1.59.0" -haze = "0.7.1" -jsunpacker = "1.0.2" +firebase-android-crashlytics = "19.0.1" +flowredux = "1.2.2" +haze = "0.7.2" +immutable = "0.3.7" kache = "2.1.0" -kcef = "2024.01.07.1" +kast = "0.2.1" kmpalette = "3.1.0" -kodein = "7.21.2" -kolor = "1.5.1" -kotlin = "1.9.23" -ksp = "1.9.23-1.0.20" -ktor = "2.3.11" -ktorfit = "1.13.0" -ktsoup = "0.3.0" -lang = "3.14.0" -material = "1.12.0" +kodein = "7.22.0" +kolor = "1.6.0" +konfig = "0.15.1" +kotlin = "2.0.0" +ksoup = "0.1.2" +ksp = "2.0.0-1.0.22" +ktor = "2.3.12" +ktorfit = "1.14.0" media3 = "1.3.1" -moko-resources = "0.23.0" +moko-resources = "0.24.0" multidex = "2.0.1" nanoid = "1.0.1" napier = "2.7.1" +oidc = "0.10.0" okhttp = "4.12.0" -osdetector = "1.7.3" -protobuf = "0.9.4" -protoc = "3.24.4" -realm = "1.13.0" -sekret = "0.2.1" -serialization-json = "1.6.3" -skeo = "0.1.1" -splashscreen = "1.0.1" +qrose = "1.0.1" +sekret = "2.0.0-alpha-05" +serialization = "1.7.1" +skeo = "0.2.1" sqldelight = "2.0.2" -turbine = "1.0.0" +splashscreen = "1.0.1" +tooling = "1.6.1" versions = "0.51.0" -vlcj = "4.8.2" -windowsize-multiplatform = "0.5.0" +vlcj = "4.8.3" +webview = "0.33.6" +windowsize = "0.5.0" [libraries] -aboutlibraries = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutlibraries" } -accompanist-uicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } -activity = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" } -activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } +aboutlibraries = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutlibraries" } android = { group = "androidx.core", name = "core-ktx", version.ref = "android-core" } android-sqlite-framework = { group = "androidx.sqlite", name = "sqlite-framework", version.ref = "android-sqlite" } -appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -appdirs = { group = "net.harawata", name = "appdirs", version.ref = "appdirs" } +ackpine = { group = "ru.solrudev.ackpine", name = "ackpine-ktx", version.ref = "ackpine" } +activity = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" } +activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } +androidx-window = { module = "androidx.window:window", version.require = "1.3.0" } +apollo = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo" } +atomicfu = { group = "org.jetbrains.kotlinx", name = "atomicfu-gradle-plugin", version.ref = "atomicfu" } blurhash = { group = "com.vanniktech", name = "blurhash", version.ref = "blurhash" } coil = { group = "io.coil-kt.coil3", name = "coil", version.ref = "coil" } coil-network = { group = "io.coil-kt.coil3", name = "coil-network-ktor", version.ref = "coil" } coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } -coil-gif = { group = "io.coil-kt.coil3", name = "coil-gif", version.ref = "coil" } coil-svg = { group = "io.coil-kt.coil3", name = "coil-svg", version.ref = "coil" } -compose-ui-util = { group = "org.jetbrains.compose.ui", name = "ui-util", version.ref = "compose" } -context-menu = { group = "io.github.dzirbel", name = "compose-material-context-menu", version.ref = "context-menu" } coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "coroutines" } -coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } -coroutines-js = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core-js", version.ref = "coroutines" } -datastore = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastore" } -datastore-android = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" } +datastore = { group = "androidx.datastore", name = "datastore-core-okio", version.ref = "datastore" } datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "datetime" } decompose = { group = "com.arkivanov.decompose", name = "decompose", version.ref = "decompose" } decompose-compose = { group = "com.arkivanov.decompose", name = "extensions-compose", version.ref = "decompose" } +desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" } firebase-auth = { group = "dev.gitlive", name = "firebase-auth", version.ref = "firebase" } firebase-crashlytics = { group = "dev.gitlive", name = "firebase-crashlytics", version.ref = "firebase" } +firebase-java = { group = "dev.gitlive", name = "firebase-java-sdk", version.strictly = "0.4.3" } firebase-store = { group = "dev.gitlive", name = "firebase-firestore", version.ref = "firebase" } firebase-android = { group = "com.google.firebase", name = "firebase-common", version.ref = "firebase-android" } -firebase-android-auth = { group = "com.google.firebase", name = "firebase-auth-ktx", version.ref = "firebase-android-auth" } +firebase-android-analytics = { group = "com.google.firebase", name = "firebase-analytics", version.ref = "firebase-android-analytics" } +firebase-android-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebase-android-auth" } firebase-android-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "firebase-android-crashlytics" } -firebase-android-firestore = { group = "com.google.firebase", name = "firebase-firestore", version.ref = "firebase-android-firestore" } flowredux = { group = "com.freeletics.flowredux", name = "flowredux", version.ref = "flowredux" } -grpc = { group = "io.grpc", name = "grpc-protobuf", version.ref = "grpc" } haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } haze-materials = { group = "dev.chrisbanes.haze", name = "haze-materials", version.ref = "haze" } -jsunpacker = { group = "dev.datlag.jsunpacker", name = "jsunpacker", version.ref = "jsunpacker" } +immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "immutable" } kache = { group = "com.mayakapps.kache", name = "kache", version.ref = "kache" } +kast = { group = "dev.datlag.kast", name = "kast", version.ref = "kast" } kmpalette = { group = "com.kmpalette", name = "kmpalette-core", version.ref = "kmpalette" } kodein = { group = "org.kodein.di", name = "kodein-di", version.ref = "kodein" } kodein-compose = { group = "org.kodein.di", name = "kodein-di-framework-compose", version.ref = "kodein" } kolor = { group = "com.materialkolor", name = "material-kolor", version.ref = "kolor" } +ksoup = { group = "com.fleeksoft.ksoup", name = "ksoup", version.ref = "ksoup" } ktor = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-jvm = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } -ktor-apple = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } -ktor-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } ktor-js = { group = "io.ktor", name = "ktor-client-js", version.ref = "ktor" } +ktor-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } ktor-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-network = { group = "io.ktor", name = "ktor-network", version.ref = "ktor" } +ktor-network-tls = { group = "io.ktor", name = "ktor-network-tls", version.ref = "ktor" } ktorfit = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-lib", version.ref = "ktorfit" } ktorfit-ksp = { group = "de.jensklingenberg.ktorfit", name = "ktorfit-ksp", version.ref = "ktorfit" } -ktsoup = { group = "org.drewcarlson", name = "ktsoup-core", version.ref = "ktsoup" } -ktsoup-fs = { group = "org.drewcarlson", name = "ktsoup-fs", version.ref = "ktsoup" } -ktsoup-ktor = { group = "org.drewcarlson", name = "ktsoup-ktor", version.ref = "ktsoup" } -lang = { group = "org.apache.commons", name = "commons-lang3", version.ref = "lang" } -material = { group = "com.google.android.material", name = "material", version.ref = "material" } media3 = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } media3-dash = { group = "androidx.media3", name = "media3-exoplayer-dash", version.ref = "media3" } media3-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" } @@ -130,47 +120,47 @@ moko-resources-generator = { group = "dev.icerock.moko", name = "resources-gener multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" } nanoid = { group = "dev.datlag.nanoid", name = "nanoid", version.ref = "nanoid" } napier = { group = "io.github.aakira", name = "napier", version.ref = "napier" } +oidc = { group = "io.github.kalinjul.kotlin.multiplatform", name = "oidc-appsupport", version.ref = "oidc" } +oidc-tokenstore = { group = "io.github.kalinjul.kotlin.multiplatform", name = "oidc-tokenstore", version.ref = "oidc" } okhttp-doh = { group = "com.squareup.okhttp3", name = "okhttp-dnsoverhttps", version.ref = "okhttp" } -permission = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" } -protobuf = { group = "com.google.protobuf", name = "protobuf-kotlin", version.ref = "protoc" } -protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protoc" } -realm = { group = "io.realm.kotlin", name = "library-base", version.ref = "realm" } -realm-sync = { group = "io.realm.kotlin", name = "library-sync", version.ref = "realm" } +qrose = { group = "io.github.alexzhirkevich", name = "qrose", version.ref = "qrose" } sekret = { group = "dev.datlag.sekret", name = "sekret", version.ref = "sekret" } -serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization-json" } +serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "serialization" } +serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } +serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-protobuf", version.ref = "serialization" } skeo = { group = "dev.datlag.skeo", name = "skeo", version.ref = "skeo" } -splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashscreen" } -stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } sqldelight-coroutines = { group = "app.cash.sqldelight", name = "coroutines-extensions", version.ref = "sqldelight" } sqldelight-android = { group = "app.cash.sqldelight", name = "android-driver", version.ref = "sqldelight" } sqldelight-jvm = { group = "app.cash.sqldelight", name = "sqlite-driver", version.ref = "sqldelight" } sqldelight-native = { group = "app.cash.sqldelight", name = "native-driver", version.ref = "sqldelight" } -sqldelight-adapter-bool = { group = "app.cash.sqldelight", name = "primitive-adapters", version.ref = "sqldelight" } -turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } -test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } +sqldelight-adapter = { group = "app.cash.sqldelight", name = "primitive-adapters", version.ref = "sqldelight" } +splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashscreen" } +tooling = { group = "dev.datlag.tooling", name = "tooling-async", version.ref = "tooling" } +tooling-country = { group = "dev.datlag.tooling", name = "tooling-country", version.ref = "tooling" } +tooling-decompose = { group = "dev.datlag.tooling", name = "tooling-decompose", version.ref = "tooling" } vlcj = { group = "uk.co.caprica", name = "vlcj", version.ref = "vlcj" } -webview-android = { group = "com.google.accompanist", name = "accompanist-webview", version.ref = "accompanist" } -webview-desktop = { group = "dev.datlag", name = "kcef", version.ref = "kcef" } -windowsize-multiplatform = { group = "dev.chrisbanes.material3", name = "material3-window-size-class-multiplatform", version.ref = "windowsize-multiplatform" } +webview = { group = "io.github.kevinnzou", name = "compose-webview", version.ref = "webview" } +windowsize = { group = "dev.chrisbanes.material3", name = "material3-window-size-class-multiplatform", version.ref = "windowsize" } + +[bundles] +android-media = [ "media3", "media3-dash", "media3-hls", "media3-rtsp", "media3-session", "media3-smooth", "media3-ui", "media3-cast" ] [plugins] aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } android-application = { id = "com.android.application", version.ref = "android" } android-library = { id = "com.android.library", version.ref = "android" } +apollo = { id = "com.apollographql.apollo3", version.ref = "apollo" } cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } compose = { id = "org.jetbrains.compose", version.ref = "compose" } -complete-kotlin = { id = "com.louiscad.complete-kotlin", version.ref = "complete-kotlin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics-plugin" } +konfig = { id = "com.codingfeline.buildkonfig", version.ref = "konfig" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" } moko-resources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko-resources" } multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -osdetector = { id = "com.google.osdetector", version.ref = "osdetector" } -parcelize = { id = "kotlin-parcelize", version.ref = "kotlin" } -protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } -realm = { id = "io.realm.kotlin", version.ref = "realm" } sekret = { id = "dev.datlag.sekret", version.ref = "sekret" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } -versions = { id = "com.github.ben-manes.versions", version.ref = "versions" } \ No newline at end of file +versions = { id = "com.github.ben-manes.versions", version.ref = "versions" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832..e6441136 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586a..54beadda 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew index a69d9cb6..43bed5da 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,8 @@ -#!/bin/sh + +#!/usr/bin/env sh # -# Copyright © 2015-2021 the original authors. +# Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,101 +18,67 @@ # ############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# +## +## Gradle start up script for UN*X +## ############################################################################## # Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac +# Resolve links: ${'$'}0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)${'$'}'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} +APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum +MAX_FD="maximum" warn () { - echo "$*" -} >&2 + echo "${'$'}*" +} die () { echo - echo "$*" + echo "${'$'}*" echo exit 1 -} >&2 +} # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -121,9 +88,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java + JAVACMD="$JAVA_HOME/jre/sh/java" else - JAVACMD=$JAVA_HOME/bin/java + JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -132,7 +99,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD=java + JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -140,101 +107,80 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi # For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg + i=`expr $i + 1` done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 index f127cfd4..e34f4911 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,4 @@ + @rem @rem Copyright 2015 the original author or authors. @rem @@ -14,7 +15,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%"=="" @echo off +@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. +if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,15 +76,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd +if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..64c6aaf3 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,358 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93A953A29CC810C00F8E227 /* iosApp.swift */; }; + A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A953E29CC810D00F8E227 /* Assets.xcassets */; }; + A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A93A954129CC810D00F8E227 /* Preview Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A93A953729CC810C00F8E227 /* Burning-Series.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Burning-Series.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + A93A953A29CC810C00F8E227 /* iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosApp.swift; sourceTree = ""; }; + A93A953E29CC810D00F8E227 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A93A954129CC810D00F8E227 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A93A953429CC810C00F8E227 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A93A952E29CC810C00F8E227 = { + isa = PBXGroup; + children = ( + A93A953929CC810C00F8E227 /* iosApp */, + A93A953829CC810C00F8E227 /* Products */, + C4127409AE3703430489E7BC /* Frameworks */, + ); + sourceTree = ""; + }; + A93A953829CC810C00F8E227 /* Products */ = { + isa = PBXGroup; + children = ( + A93A953729CC810C00F8E227 /* Burning-Series.app */, + ); + name = Products; + sourceTree = ""; + }; + A93A953929CC810C00F8E227 /* iosApp */ = { + isa = PBXGroup; + children = ( + A93A953A29CC810C00F8E227 /* iosApp.swift */, + A93A953E29CC810D00F8E227 /* Assets.xcassets */, + A93A954029CC810D00F8E227 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; + A93A954029CC810D00F8E227 /* Preview Content */ = { + isa = PBXGroup; + children = ( + A93A954129CC810D00F8E227 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + C4127409AE3703430489E7BC /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A93A953629CC810C00F8E227 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + A9D80A052AAB5CDE006C8738 /* ShellScript */, + A93A953329CC810C00F8E227 /* Sources */, + A93A953429CC810C00F8E227 /* Frameworks */, + A93A953529CC810C00F8E227 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = A93A953729CC810C00F8E227 /* Burning-Series.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A93A952F29CC810C00F8E227 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1420; + TargetAttributes = { + A93A953629CC810C00F8E227 = { + CreatedOnToolsVersion = 14.2; + }; + }; + }; + buildConfigurationList = A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A93A952E29CC810C00F8E227; + productRefGroup = A93A953829CC810C00F8E227 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A93A953629CC810C00F8E227 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A93A953529CC810C00F8E227 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A93A954229CC810D00F8E227 /* Preview Assets.xcassets in Resources */, + A93A953F29CC810D00F8E227 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A9D80A052AAB5CDE006C8738 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A93A953329CC810C00F8E227 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A93A953B29CC810C00F8E227 /* iosApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A93A954329CC810D00F8E227 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A93A954429CC810D00F8E227 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + A93A954629CC810D00F8E227 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.datlag.burningseries.iosApp; + PRODUCT_NAME = "Burning-Series"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A93A954729CC810D00F8E227 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.datlag.burningseries.iosApp; + PRODUCT_NAME = "Burning-Series"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A93A953229CC810C00F8E227 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A93A954329CC810D00F8E227 /* Debug */, + A93A954429CC810D00F8E227 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A93A954529CC810D00F8E227 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A93A954629CC810D00F8E227 /* Debug */, + A93A954729CC810D00F8E227 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A93A952F29CC810C00F8E227 /* Project object */; +} diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/iosApp.swift b/iosApp/iosApp/iosApp.swift new file mode 100644 index 00000000..fbf3ac08 --- /dev/null +++ b/iosApp/iosApp/iosApp.swift @@ -0,0 +1,19 @@ +import UIKit +import ComposeApp + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + if let window = window { + window.rootViewController = MainKt.MainViewController() + window.makeKeyAndVisible() + } + return true + } +} diff --git a/k2k/build.gradle.kts b/k2k/build.gradle.kts new file mode 100644 index 00000000..ca221b35 --- /dev/null +++ b/k2k/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.serialization) +} + +kotlin { + jvm() + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + api(libs.immutable) + implementation(libs.coroutines) + implementation(libs.ktor) + implementation(libs.ktor.network) + implementation(libs.ktor.network.tls) + implementation(libs.serialization.json) + implementation(libs.tooling) + } + } +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Constants.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Constants.kt new file mode 100644 index 00000000..a43477bd --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Constants.kt @@ -0,0 +1,12 @@ +package dev.datlag.burningseries.k2k + +import kotlinx.serialization.json.Json + +internal data object Constants { + val json: Json = Json { + isLenient = true + } + + const val BROADCAST_ADDRESS = "255.255.255.255" + const val BROADCAST_SOCKET = "0.0.0.0" +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Host.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Host.kt new file mode 100644 index 00000000..ab9ab042 --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/Host.kt @@ -0,0 +1,17 @@ +package dev.datlag.burningseries.k2k + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlin.properties.Delegates + +@Serializable +data class Host( + @SerialName("name") val name: String, + @SerialName("filterMatch") val filterMatch: String = "" +) { + @Transient + lateinit var hostAddress: String + + var port by Delegates.notNull() +} diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.kt new file mode 100644 index 00000000..d4c08849 --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.kt @@ -0,0 +1,9 @@ +package dev.datlag.burningseries.k2k + +import kotlinx.collections.immutable.ImmutableSet + +expect object NetInterface { + // udp, no isLoopback, broadcastAddress is not null + fun getAddresses(): ImmutableSet + fun getLocalAddress(): String +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/Connection.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/Connection.kt new file mode 100644 index 00000000..7df868cd --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/Connection.kt @@ -0,0 +1,53 @@ +package dev.datlag.burningseries.k2k.connect + +import dev.datlag.burningseries.k2k.Host +import dev.datlag.burningseries.k2k.discover.Discovery +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlin.properties.Delegates + +class Connection private constructor( + private val port: Int, + private val scope: CoroutineScope +) { + + suspend fun send(bytes: ByteArray, peer: Host) = ConnectionClient.send(bytes, peer, port) + + fun startReceiving(listener: suspend (ByteArray) -> Unit) { + ConnectionServer.startServer(port, scope, listener) + } + + fun stopReceiving() { + ConnectionServer.stopServer() + } + + class Builder(private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { + private var port by Delegates.notNull() + + fun setPort(port: Int) = apply { + this.port = port + } + + fun setScope(scope: CoroutineScope) = apply { + this.scope = scope + } + + fun build() = Connection(port, scope) + } +} + +fun CoroutineScope.connection(builder: Connection.Builder.() -> Unit) = Connection.Builder(this).apply(builder).build() \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionClient.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionClient.kt new file mode 100644 index 00000000..03dc1342 --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionClient.kt @@ -0,0 +1,30 @@ +package dev.datlag.burningseries.k2k.connect + +import dev.datlag.burningseries.k2k.Host +import dev.datlag.tooling.async.suspendCatching +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openWriteChannel +import kotlinx.coroutines.Dispatchers + +internal data object ConnectionClient { + suspend fun send( + bytes: ByteArray, + host: Host, + port: Int + ) { + suspendCatching { + val socketAddress = InetSocketAddress(host.hostAddress, port) + val socket = aSocket(SelectorManager(Dispatchers.IO)) + .tcp() + .connect(socketAddress) { + socketTimeout = 20000 + reuseAddress = true + } + + val writeChannel = socket.openWriteChannel(autoFlush = true) + writeChannel.writeFully(bytes, 0, bytes.size) + } + } +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionServer.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionServer.kt new file mode 100644 index 00000000..93c26e2f --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/connect/ConnectionServer.kt @@ -0,0 +1,59 @@ +package dev.datlag.burningseries.k2k.connect + +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.utils.io.core.use +import io.ktor.utils.io.readAvailable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import dev.datlag.burningseries.k2k.NetInterface +import dev.datlag.tooling.async.suspendCatching +import kotlinx.coroutines.flow.update + +internal data object ConnectionServer { + private var socket = aSocket(SelectorManager(Dispatchers.IO)).tcp() + private var receiveJob: Job? = null + + fun startServer( + port: Int, + scope: CoroutineScope, + listener: suspend (ByteArray) -> Unit + ) { + receiveJob?.cancel() + receiveJob = scope.launch(Dispatchers.IO) { + while (true) { + val socketAddress = InetSocketAddress(NetInterface.getLocalAddress(), port) + + socket.bind(socketAddress) { + reuseAddress = true + reusePort = true + }.accept().use { boundSocket -> + suspendCatching { + val readChannel = boundSocket.openReadChannel() + val buffer = ByteArray(readChannel.availableForRead) + while (true) { + val bytesRead = readChannel.readAvailable(buffer) + if (bytesRead <= 0) { + break + } + + listener(buffer) + } + }.onFailure { + boundSocket.close() + } + } + } + } + } + + fun stopServer() { + receiveJob?.cancel() + socket = aSocket(SelectorManager(Dispatchers.IO)).tcp() + } +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/Discovery.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/Discovery.kt new file mode 100644 index 00000000..043ed54f --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/Discovery.kt @@ -0,0 +1,149 @@ +package dev.datlag.burningseries.k2k.discover + +import dev.datlag.burningseries.k2k.Constants +import dev.datlag.burningseries.k2k.Host +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlin.properties.Delegates +import kotlin.time.Duration + +class Discovery private constructor( + private val discoveryTimeout: Long, + private val discoveryTimeoutListener: (suspend () -> Unit)?, + private val discoverableTimeout: Long, + private val discoverableTimeoutListener: (suspend () -> Unit)?, + private val discoverPing: Long, + private val port: Int, + private val hostFilter: Regex, + private val hostIsClient: Boolean, + private val scope: CoroutineScope +) { + + private var discoverableTimer: Job? = null + private var discoveryTimer: Job? = null + + val peers: StateFlow> = DiscoveryServer.hosts + + fun makeDiscoverable(host: Host) { + val sendDataString = Constants.json.encodeToString(host) + DiscoveryClient.startBroadcasting(port, discoverPing, sendDataString.encodeToByteArray(), scope) + discoverableTimer?.cancel() + + if (discoverableTimeout > 0L) { + discoverableTimer = scope.launch(Dispatchers.IO) { + delay(discoverableTimeout) + discoverableTimeoutListener?.invoke() + stopBeingDiscoverable() + } + } + } + + @JvmOverloads + fun makeDiscoverable( + hostName: String, + filterMatch: String = "" + ) = makeDiscoverable(Host(hostName, filterMatch)) + + fun stopBeingDiscoverable() { + DiscoveryClient.stopBroadcasting() + discoverableTimer?.cancel() + } + + @JvmOverloads + fun startDiscovery(hostIsClient: Boolean = this.hostIsClient) { + DiscoveryServer.startListening(port, discoverPing, hostFilter, hostIsClient, scope) + discoveryTimer?.cancel() + + if (discoveryTimeout > 0L) { + discoveryTimer = scope.launch(Dispatchers.IO) { + delay(discoveryTimeout) + discoveryTimeoutListener?.invoke() + stopDiscovery() + } + } + } + + fun stopDiscovery() { + DiscoveryServer.stopListening() + discoveryTimer?.cancel() + } + + class Builder(private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { + private var discoveryTimeout by Delegates.notNull() + private var discoveryTimeoutListener: (suspend () -> Unit)? = null + private var discoverableTimeout by Delegates.notNull() + private var discoverableTimeoutListener: (suspend () -> Unit)? = null + private var discoverPing: Long = 1000L + private var port by Delegates.notNull() + private var hostFilter: Regex = Regex("^$") + private var hostIsClientToo = false + + fun setDiscoveryTimeout(timeoutMilli: Long) = apply { + this.discoveryTimeout = timeoutMilli + } + + fun setDiscoveryTimeout(duration: Duration) = apply { + this.discoveryTimeout = duration.inWholeMilliseconds + } + + fun setDiscoveryTimeoutListener(listener: suspend () -> Unit) = apply { + this.discoveryTimeoutListener = listener + } + + fun setDiscoverableTimeout(timeoutMilli: Long) = apply { + this.discoverableTimeout = timeoutMilli + } + + fun setDiscoverableTimeout(duration: Duration) = apply { + this.discoverableTimeout = duration.inWholeMilliseconds + } + + fun setDiscoverableTimeoutListener(listener: suspend () -> Unit) = apply { + this.discoverableTimeoutListener = listener + } + + fun setPing(intervalMilli: Long) = apply { + this.discoverPing = intervalMilli + } + + fun setPing(duration: Duration) = apply { + this.discoverPing = duration.inWholeMilliseconds + } + + fun setPort(port: Int) = apply { + this.port = port + } + + fun setHostFilter(filter: Regex) = apply { + this.hostFilter = filter + } + + fun setScope(scope: CoroutineScope) = apply { + this.scope = scope + } + + fun setHostIsClient(`is`: Boolean) = apply { + this.hostIsClientToo = `is` + } + + fun build() = Discovery( + discoveryTimeout, + discoveryTimeoutListener, + discoverableTimeout, + discoverableTimeoutListener, + discoverPing, + port, + hostFilter, + hostIsClientToo, + scope + ) + } +} + +fun CoroutineScope.discovery(builder: Discovery.Builder.() -> Unit) = Discovery.Builder(this).apply(builder).build() \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryClient.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryClient.kt new file mode 100644 index 00000000..829ea136 --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryClient.kt @@ -0,0 +1,63 @@ +package dev.datlag.burningseries.k2k.discover + +import dev.datlag.burningseries.k2k.Constants +import dev.datlag.tooling.async.suspendCatching +import dev.datlag.tooling.scopeCatching +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openWriteChannel +import io.ktor.utils.io.close +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import dev.datlag.burningseries.k2k.NetInterface + +internal data object DiscoveryClient { + private var socket = aSocket(SelectorManager(Dispatchers.IO)).udp() + + private var broadcastJob: Job? = null + + internal fun startBroadcasting( + port: Int, + ping: Long, + data: ByteArray, + scope: CoroutineScope + ) { + broadcastJob?.cancel() + broadcastJob = scope.launch(Dispatchers.IO) { + while (currentCoroutineContext().isActive) { + send(port, data) + delay(ping) + } + } + } + + internal fun stopBroadcasting() { + broadcastJob?.cancel() + socket = aSocket(SelectorManager(Dispatchers.IO)).udp() + } + + private suspend fun send(port: Int, data: ByteArray) { + suspend fun writeToSocket(address: String, port: Int) = suspendCatching { + val socketConnection = socket.connect(InetSocketAddress(address, port)) { + broadcast = true + reuseAddress = true + } + + val output = socketConnection.openWriteChannel(autoFlush = true) + output.writeFully(data, 0, data.size) + output.close() + socketConnection.close() + } + + writeToSocket(Constants.BROADCAST_ADDRESS, port) + for (address in NetInterface.getAddresses()) { + writeToSocket(address, port) + } + } +} \ No newline at end of file diff --git a/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryServer.kt b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryServer.kt new file mode 100644 index 00000000..4cf8df0e --- /dev/null +++ b/k2k/src/commonMain/kotlin/dev/datlag/burningseries/k2k/discover/DiscoveryServer.kt @@ -0,0 +1,98 @@ +package dev.datlag.burningseries.k2k.discover + +import dev.datlag.burningseries.k2k.Constants +import dev.datlag.burningseries.k2k.Host +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.aSocket +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext +import dev.datlag.burningseries.k2k.NetInterface +import dev.datlag.tooling.scopeCatching +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.openReadChannel +import io.ktor.utils.io.core.readUTF8Line +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.consumeEach +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +internal data object DiscoveryServer { + private var socket = aSocket(SelectorManager(Dispatchers.IO)).udp() + + private val currentHostIPs = MutableStateFlow>(persistentSetOf()) + internal val hosts = MutableStateFlow>(persistentSetOf()) + private var listenJob: Job? = null + + internal fun startListening( + port: Int, + ping: Long, + hostFilter: Regex, + hostIsClient: Boolean, + scope: CoroutineScope + ) { + listenJob?.cancel() + listenJob = scope.launch(Dispatchers.IO) { + updateCurrentDeviceIPs() + listen(port, ping, hostFilter, hostIsClient) + } + } + + internal fun stopListening() { + listenJob?.cancel() + socket = aSocket(SelectorManager(Dispatchers.IO)).udp() + currentHostIPs.update { persistentSetOf() } + hosts.update { persistentSetOf() } + } + + private suspend fun updateCurrentDeviceIPs() { + currentHostIPs.update { NetInterface.getAddresses() } + } + + private suspend fun listen(port: Int, ping: Long, filter: Regex, hostIsClient: Boolean) { + val socketAddress = InetSocketAddress(Constants.BROADCAST_SOCKET, port) + val serverSocket = socket.bind(socketAddress) { + broadcast = true + reuseAddress = true + } + + while (true) { + serverSocket.openReadChannel() + serverSocket.incoming.consumeEach { datagram -> + try { + val receivedPacket = datagram.packet.readUTF8Line() + if (!receivedPacket.isNullOrBlank()) { + val host = Constants.json.decodeFromString(receivedPacket).apply { + val inetSocketAddress = datagram.address as InetSocketAddress + this.hostAddress = inetSocketAddress.hostname + this.port = inetSocketAddress.port + } + + val keepHosts = hosts.value.toMutableSet() + + if (hostIsClient || !currentHostIPs.value.contains(host.hostAddress)) { + if (host.filterMatch.matches(filter)) { + keepHosts.add(host) + } + } + + hosts.update { keepHosts.toImmutableSet() } + } + } catch (e: Throwable) { + serverSocket.close() + if (e is CancellationException) { + throw e + } + } + } + + delay(ping) + } + } +} \ No newline at end of file diff --git a/k2k/src/jvmMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.jvm.kt b/k2k/src/jvmMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.jvm.kt new file mode 100644 index 00000000..3cafa358 --- /dev/null +++ b/k2k/src/jvmMain/kotlin/dev/datlag/burningseries/k2k/NetInterface.jvm.kt @@ -0,0 +1,38 @@ +package dev.datlag.burningseries.k2k + +import dev.datlag.tooling.scopeCatching +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.CancellationException +import java.net.InetAddress +import java.net.NetworkInterface + +actual object NetInterface { + actual fun getAddresses(): ImmutableSet { + val interfaces = NetworkInterface.getNetworkInterfaces() + val updatedIPs = mutableSetOf() + + while (interfaces.hasMoreElements()) { + val networkInterface = interfaces.nextElement() + try { + if (networkInterface.isLoopback || !networkInterface.isUp) continue + + networkInterface.interfaceAddresses.forEach { + if (it.broadcast != null) { + updatedIPs.add(it.broadcast.hostAddress) + } + } + } catch (e: Throwable) { + if (e is CancellationException) { + throw e + } + } + } + + return updatedIPs.toImmutableSet() + } + + actual fun getLocalAddress(): String { + return InetAddress.getLocalHost().hostAddress + } +} \ No newline at end of file diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock deleted file mode 100644 index de95da1a..00000000 --- a/kotlin-js-store/yarn.lock +++ /dev/null @@ -1,3608 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@firebase/analytics-compat@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.1.6.tgz#a5a8c909c67128d89c0aaa1c87699d1c5e873746" - integrity sha512-xvdp4/zwOG1f+v9JSpfCQoPJ98HcJR42cEnZ9pRIQLmUy7L7QceIuaF3m+zVtoqa4agBQnJ1dhe58FshOFKOPw== - dependencies: - "@firebase/analytics" "0.7.5" - "@firebase/analytics-types" "0.7.0" - "@firebase/component" "0.5.10" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/analytics-types@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.7.0.tgz#91960e7c87ce8bf18cf8dd9e55ccbf5dc3989b5d" - integrity sha512-DNE2Waiwy5+zZnCfintkDtBfaW6MjIG883474v6Z0K1XZIvl76cLND4iv0YUb48leyF+PJK1KO2XrgHb/KpmhQ== - -"@firebase/analytics@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.7.5.tgz#c12a2ea10067e8e0947bc54758750c65a700e79f" - integrity sha512-vrKDh84hBbKPJaU2oAZDewyC79D8opJOQZ5AU3BXBBwEfRjKt3C3jj/Vl6aJUme+RKXlomTw3xcHIOoPzTgBVA== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/installations" "0.5.5" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/app-check-compat@0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.2.3.tgz#f6ee8b8581423fe4efbba6165c14c3f9625a95e4" - integrity sha512-e2mKkuecr1XgsyTGXKfg83PcV1UdT7+tXYoHIjeBeLrP5gGL4OQbWCzzt6uVQpk1gmJbUktje/rd6Et6cdL+wg== - dependencies: - "@firebase/app-check" "0.5.3" - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/app-check-interop-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.0.tgz#83afd9d41f99166c2bdb2d824e5032e9edd8fe53" - integrity sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA== - -"@firebase/app-check@0.5.3": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.5.3.tgz#477ea3d925bde898dce1c25bc3d3886252ad2aaf" - integrity sha512-M2/UO5PgxHCl0wPYWGdF6lO8nqclwuRMCIrc+75xv3/Dr3hhUu4ztF5JNaAV5tktSCt1UrnASG+4rNVifCzSRw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/app-compat@0.1.14": - version "0.1.14" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.1.14.tgz#699d364893680ed0aa4202607662b7497976b423" - integrity sha512-CvT7/TdfWNRudrExAyWiPcMVtaqljE4mch/KfmfSz1mGmK0j/y1DN6PDJ+NZxkI+Za+YRkOI55H6DdIBsYQ0Qg== - dependencies: - "@firebase/app" "0.7.13" - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/app-types@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.7.0.tgz#c9e16d1b8bed1a991840b8d2a725fb58d0b5899f" - integrity sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg== - -"@firebase/app@0.7.13": - version "0.7.13" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.7.13.tgz#e725f8ff23a95f1e3ba2ad58308fc2ab3f16cf89" - integrity sha512-nMnz+lxASVZrWcAgLIgvs2QcsySjYvNpGjDeyhMzrbyBoBLgTux0cGWtm5RrJKx7arqueRpIihxcJtKAzCcIsw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/auth-compat@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.2.5.tgz#f3b08f6e90d4357c2ca44a38169a2b5a2a028fd1" - integrity sha512-Ft9PkmWOioxPMts6CMopN7sHpSXipQigOdm4BQ5HYTGHyLZpid2cj+2LxWsOYqQlhA1YBtzwE7sBRpV0W6bblQ== - dependencies: - "@firebase/auth" "0.19.5" - "@firebase/auth-types" "0.11.0" - "@firebase/component" "0.5.10" - "@firebase/util" "1.4.3" - node-fetch "2.6.5" - selenium-webdriver "^4.0.0-beta.2" - tslib "^2.1.0" - -"@firebase/auth-interop-types@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964" - integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g== - -"@firebase/auth-types@0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.11.0.tgz#b9c73c60ca07945b3bbd7a097633e5f78fa9e886" - integrity sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw== - -"@firebase/auth@0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.19.5.tgz#01130ccfb8b67abda37be2650923b9b9ce593544" - integrity sha512-3+9XUnxaNb+ck6yULtEwOZbikWpL9KXuNLR34GxRv3mpOKD3uNbbONT149zMo3C6asI1bdv4+hCM78aS8VhZ0w== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - node-fetch "2.6.5" - selenium-webdriver "4.0.0-rc-1" - tslib "^2.1.0" - -"@firebase/component@0.5.10": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.5.10.tgz#eab8acfd9b1a2b6534a63cbcd7cbc7660c47663d" - integrity sha512-mzUpg6rsBbdQJvAdu1rNWabU3O7qdd+B+/ubE1b+pTbBKfw5ySRpRRE6sKcZ/oQuwLh0HHB6FRJHcylmI7jDzA== - dependencies: - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/database-compat@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-0.1.5.tgz#df451ce6a8d0ea2ebc2b1c8017da7d773424371d" - integrity sha512-UVxkHL24sZfsjsjs+yiKIdYdrWXHrLxSFCYNdwNXDlTkAc0CWP9AAY3feLhBVpUKk+4Cj0I4sGnyIm2C1ltAYg== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/database" "0.12.5" - "@firebase/database-types" "0.9.4" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/database-types@0.9.4": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.9.4.tgz#08b7da33d8dca8f5adab45bfb1cdf8654f2c6720" - integrity sha512-uAQuc6NUZ5Oh/cWZPeMValtcZ+4L1stgKOeYvz7mLn8+s03tnCDL2N47OLCHdntktVkhImQTwGNARgqhIhtNeA== - dependencies: - "@firebase/app-types" "0.7.0" - "@firebase/util" "1.4.3" - -"@firebase/database@0.12.5": - version "0.12.5" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.12.5.tgz#e7f8e8052c3038aff79633e5395c3cc5c30ca7b2" - integrity sha512-1Pd2jYqvqZI7SQWAiXbTZxmsOa29PyOaPiUtr8pkLSfLp4AeyMBegYAXCLYLW6BNhKn3zNKFkxYDxYHq4q+Ixg== - dependencies: - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - faye-websocket "0.11.4" - tslib "^2.1.0" - -"@firebase/firestore-compat@0.1.12": - version "0.1.12" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.1.12.tgz#f7f4fcde9460d99a013cc454e6cc3c837425f9d0" - integrity sha512-+8FwiYctRc5Vwa59iGD6IdTNCKqgZYB6yl/PvDJfi+WNhJbMznpHYWBI+urNGHAXBpHRDCwJS08LVsVTsBsS0w== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/firestore" "3.4.3" - "@firebase/firestore-types" "2.5.0" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/firestore-types@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-2.5.0.tgz#16fca40b6980fdb000de86042d7a96635f2bcdd7" - integrity sha512-I6c2m1zUhZ5SH0cWPmINabDyH5w0PPFHk2UHsjBpKdZllzJZ2TwTkXbDtpHUZNmnc/zAa0WNMNMvcvbb/xJLKA== - -"@firebase/firestore@3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-3.4.3.tgz#14254843eb2fb5d0d7bad7c92a9ea11b39394207" - integrity sha512-mUZY/aTKpliCyoYs7/64olumeTbM42axu2u8QDl28AX+4q7vHGIiks9+H2gaqz/zgWODXiQeBmJlHCb1RlJGhQ== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - "@firebase/webchannel-wrapper" "0.6.1" - "@grpc/grpc-js" "^1.3.2" - "@grpc/proto-loader" "^0.6.0" - node-fetch "2.6.5" - tslib "^2.1.0" - -"@firebase/functions-compat@0.1.8": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.1.8.tgz#ca29ac0970c8de0af0a49f56b1cf7f2527583d0f" - integrity sha512-9nB6uPzSbnzOE+V7USbHsQxze/xeJC5WTgBOhyHA8eEU/z5mBGfD1eV31QbI7mbSFL8m4N8F5cidDw3zB1G/Jw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/functions" "0.7.7" - "@firebase/functions-types" "0.5.0" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/functions-types@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.5.0.tgz#b50ba95ccce9e96f7cda453228ffe1684645625b" - integrity sha512-qza0M5EwX+Ocrl1cYI14zoipUX4gI/Shwqv0C1nB864INAD42Dgv4v94BCyxGHBg2kzlWy8PNafdP7zPO8aJQA== - -"@firebase/functions@0.7.7": - version "0.7.7" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.7.7.tgz#27c0b616847241c6008d7430b5e5b01abffb294f" - integrity sha512-e944UigvrqwGHODww8QU1oaZ+KFdqcf/hmf5L2vEakQEIOjCRy6Kal8xAlYpaP4QbC1DEUfY4qC9QoFUErI2fQ== - dependencies: - "@firebase/app-check-interop-types" "0.1.0" - "@firebase/auth-interop-types" "0.1.6" - "@firebase/component" "0.5.10" - "@firebase/messaging-interop-types" "0.1.0" - "@firebase/util" "1.4.3" - node-fetch "2.6.5" - tslib "^2.1.0" - -"@firebase/installations@0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.5.5.tgz#b517e20fe4ce4b9e2f3349596391d54f2294bf7f" - integrity sha512-mYWUxYXPlxcR0YOikPw88TjIS2NK35Z0ivkJL0+FevNnVIsqwGSe12AtPlZB/kzjB0RtHoKW+cWC0V9xiTgJ3Q== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/util" "1.4.3" - idb "3.0.2" - tslib "^2.1.0" - -"@firebase/logger@0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.3.2.tgz#5046ffa8295c577846d54b6ca95645a03809800e" - integrity sha512-lzLrcJp9QBWpo40OcOM9B8QEtBw2Fk1zOZQdvv+rWS6gKmhQBCEMc4SMABQfWdjsylBcDfniD1Q+fUX1dcBTXA== - dependencies: - tslib "^2.1.0" - -"@firebase/messaging-compat@0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.1.6.tgz#840069b620e51adc34e0080d72ccad4bca4b3502" - integrity sha512-VzNM5ew8YAH7tzyukY0QqrCKdmaIe1FsWJSNPWcfzMNri8mpfKALIjeFzle+6DrRWZweFsp8ejvcvvulIDILGw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/messaging" "0.9.6" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/messaging-interop-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.1.0.tgz#bdac02dd31edd5cb9eec37b1db698ea5e2c1a631" - integrity sha512-DbvUl/rXAZpQeKBnwz0NYY5OCqr2nFA0Bj28Fmr3NXGqR4PAkfTOHuQlVtLO1Nudo3q0HxAYLa68ZDAcuv2uKQ== - -"@firebase/messaging@0.9.6": - version "0.9.6" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.9.6.tgz#935e8cb46ba1dcbb7dabe877d1f94a874a3813aa" - integrity sha512-weDGzgU0MNtC6FCFJu/AW+pXbuX/YasHqR42NcLyoHNL8EgjXLPC0EYeMi7B8dY7MCsbc5lbPtqiveOP97L1jQ== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/installations" "0.5.5" - "@firebase/messaging-interop-types" "0.1.0" - "@firebase/util" "1.4.3" - idb "3.0.2" - tslib "^2.1.0" - -"@firebase/performance-compat@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.1.5.tgz#e5e13629740ad28fdcbb7bb4e6e49160bbe71550" - integrity sha512-s9mqR0GXJaqvIZD/GsshacpKOGa3NP6Yht33mNEtpL7ERqj35mvD1CBoUwH52eMYAaxlQd9y9JrphQgK3EmWWw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/performance" "0.5.5" - "@firebase/performance-types" "0.1.0" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/performance-types@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.1.0.tgz#5e6efa9dc81860aee2cb7121b39ae8fa137e69fc" - integrity sha512-6p1HxrH0mpx+622Ql6fcxFxfkYSBpE3LSuwM7iTtYU2nw91Hj6THC8Bc8z4nboIq7WvgsT/kOTYVVZzCSlXl8w== - -"@firebase/performance@0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.5.5.tgz#2ec0167f4d318f05a5a4fe6daac3977cc1bc598d" - integrity sha512-eA8mEKVnyY64fwAKxHbJF5t1hNkdR0EZVib0LfEWl/2elPmFcjik097hqLHzdFE88JYCxNGfFaSPo9Lbk/qe6A== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/installations" "0.5.5" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/polyfill@0.3.36": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.36.tgz#c057cce6748170f36966b555749472b25efdb145" - integrity sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg== - dependencies: - core-js "3.6.5" - promise-polyfill "8.1.3" - whatwg-fetch "2.0.4" - -"@firebase/remote-config-compat@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.1.5.tgz#560aef7ce56d235ac2cfbebccc65fbe9545f6574" - integrity sha512-bgpmrCGyOj46c0xNFvivcXRHlaVkbt4mX2etbF9s6jaOILPd4rBHIfAiBpKL64GGwTkrOjWO9/HZun4I01gbpg== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/logger" "0.3.2" - "@firebase/remote-config" "0.3.4" - "@firebase/remote-config-types" "0.2.0" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/remote-config-types@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.2.0.tgz#1e2759fc01f20b58c564db42196f075844c3d1fd" - integrity sha512-hqK5sCPeZvcHQ1D6VjJZdW6EexLTXNMJfPdTwbD8NrXUw6UjWC4KWhLK/TSlL0QPsQtcKRkaaoP+9QCgKfMFPw== - -"@firebase/remote-config@0.3.4": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.3.4.tgz#1197c92513130bcb1fe67c4978e6a9b034ef88be" - integrity sha512-SLlyVVNJ6DnU1AOjNrmv5u9Fge7gUwZVooyxMIkaT3Lj9MBM5MwfJsoG3UyiV4l7yI0iPj34LuKPpMJXOOcs4w== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/installations" "0.5.5" - "@firebase/logger" "0.3.2" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/storage-compat@0.1.9": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.1.9.tgz#c731f6bea4e7927c1343e369a480a924555bb5f4" - integrity sha512-FwSNw1FMH8Qk9l+nDmlamesEFVjOfmWO4B2BV4l3YRn5ibvxIvBqRQZP8TGUknHCWKM1b7dMq3C19cVxeJ77VQ== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/storage" "0.9.1" - "@firebase/storage-types" "0.6.0" - "@firebase/util" "1.4.3" - tslib "^2.1.0" - -"@firebase/storage-types@0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.6.0.tgz#0b1af64a2965af46fca138e5b70700e9b7e6312a" - integrity sha512-1LpWhcCb1ftpkP/akhzjzeFxgVefs6eMD2QeKiJJUGH1qOiows2w5o0sKCUSQrvrRQS1lz3SFGvNR1Ck/gqxeA== - -"@firebase/storage@0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.9.1.tgz#0b642b68868f54c41dfd44c36191a577d1d5bf6a" - integrity sha512-IMPZ21Mm05R9GKTgiiMpbata0tgzQTtZ2YMbVReSTx16GJTIpadXpjFzxhJMjVi/7Wq57LnSxsg9fe56IBSacw== - dependencies: - "@firebase/component" "0.5.10" - "@firebase/util" "1.4.3" - node-fetch "2.6.5" - tslib "^2.1.0" - -"@firebase/util@1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.4.3.tgz#4358cf5f18beaa9c8a1e5a5fc4c7c44a4ccd4b7b" - integrity sha512-gQJl6r0a+MElLQEyU8Dx0kkC2coPj67f/zKZrGR7z7WpLgVanhaCUqEsptwpwoxi9RMFIaebleG+C9xxoARq+Q== - dependencies: - tslib "^2.1.0" - -"@firebase/webchannel-wrapper@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.6.1.tgz#0c74724ba6e9ea6ad25a391eab60a79eaba4c556" - integrity sha512-9FqhNjKQWpQ3fGnSOCovHOm+yhhiorKEqYLAfd525jWavunDJcx8rOW6i6ozAh+FbwcYMkL7b+3j4UR/30MpoQ== - -"@grpc/grpc-js@^1.3.2": - version "1.9.11" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.11.tgz#7b21195c910a49c0bb5d0df21d28a30c4e174851" - integrity sha512-QDhMfbTROOXUhLHMroow8f3EHiCKUOh6UwxMP5S3EuXMnWMNSVIhatGZRwkpg9OUTYdZPsDUVH3cOAkWhGFUJw== - dependencies: - "@grpc/proto-loader" "^0.7.8" - "@types/node" ">=12.12.47" - -"@grpc/proto-loader@^0.6.0": - version "0.6.13" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" - integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== - dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^6.11.3" - yargs "^16.2.0" - -"@grpc/proto-loader@^0.7.8": - version "0.7.10" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" - integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.4" - yargs "^17.7.2" - -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@js-joda/core@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" - integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@socket.io/component-emitter@~3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" - integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== - -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cookie@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== - -"@types/cors@^2.8.12": - version "2.8.17" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" - integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.7" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.7.tgz#430b3cc96db70c81f405e6a08aebdb13869198f5" - integrity sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.41" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" - integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/http-proxy@^1.17.8": - version "1.17.14" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== - dependencies: - "@types/node" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/long@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== - -"@types/mime@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" - integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node-forge@^1.3.0": - version "1.3.10" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.10.tgz#62a19d4f75a8b03290578c2b04f294b1a5a71b07" - integrity sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw== - dependencies: - "@types/node" "*" - -"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": - version "20.10.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.0.tgz#16ddf9c0a72b832ec4fcce35b8249cf149214617" - integrity sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ== - dependencies: - undici-types "~5.26.4" - -"@types/qs@*": - version "6.9.10" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" - integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" - integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.1": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== - -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" - integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== - -"@webpack-cli/info@^2.0.1": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" - integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== - -"@webpack-cli/serve@^2.0.3": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" - integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -abab@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abort-controller@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-assertions@^1.7.6: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - -acorn@^8.7.1, acorn@^8.8.2: - version "8.11.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" - integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64id@2.0.0, base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^1.19.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" - integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -browserslist@^4.14.5: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== - dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" - update-browserslist-db "^1.0.13" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" - integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== - dependencies: - function-bind "^1.1.2" - get-intrinsic "^1.2.1" - set-function-length "^1.1.1" - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001541: - version "1.0.30001564" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz#eaa8bbc58c0cbccdcb7b41186df39dd2ba591889" - integrity sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@3.5.3, chokidar@^3.5.1, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -cookie@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - -core-js@3.6.5: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" - integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@~2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-select@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" - integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-what@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== - -date-format@^4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" - integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-data-property@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.535: - version "1.4.594" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.594.tgz#f69f207fba80735a44a988df42f3f439115d0515" - integrity sha512-xT1HVAu5xFn7bDfkjGQi9dNpMqGchUkebwf1GL7cZN32NSwwlHRPMSDJ1KN6HkS0bWUtndbSQZqvpQftKG2uFQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -engine.io-parser@~5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb" - integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== - -engine.io@~6.5.2: - version "6.5.4" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.4.tgz#6822debf324e781add2254e912f8568508850cdc" - integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg== - dependencies: - "@types/cookie" "^0.4.1" - "@types/cors" "^2.8.12" - "@types/node" ">=10.0.0" - accepts "~1.3.4" - base64id "2.0.0" - cookie "~0.4.1" - cors "~2.8.5" - debug "~4.3.1" - engine.io-parser "~5.2.1" - ws "~8.11.0" - -enhanced-resolve@^5.13.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== - -entities@^4.2.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -envinfo@^7.7.3: - version "7.11.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" - integrity sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg== - -es-module-lexer@^1.2.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" - integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -faye-websocket@0.11.4, faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -firebase@9.6.3: - version "9.6.3" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-9.6.3.tgz#fe22c94369eb50eba9d15e8ba2edb7100d5c1ed4" - integrity sha512-CMzv2LJGruZNtKI6pk1XLVaDC7ujIcq/S57wbC9XGllykIh86GLNPwVEWuCqCWmQDAZLyhi0t6tW/F2NX3HcPA== - dependencies: - "@firebase/analytics" "0.7.5" - "@firebase/analytics-compat" "0.1.6" - "@firebase/app" "0.7.13" - "@firebase/app-check" "0.5.3" - "@firebase/app-check-compat" "0.2.3" - "@firebase/app-compat" "0.1.14" - "@firebase/app-types" "0.7.0" - "@firebase/auth" "0.19.5" - "@firebase/auth-compat" "0.2.5" - "@firebase/database" "0.12.5" - "@firebase/database-compat" "0.1.5" - "@firebase/firestore" "3.4.3" - "@firebase/firestore-compat" "0.1.12" - "@firebase/functions" "0.7.7" - "@firebase/functions-compat" "0.1.8" - "@firebase/installations" "0.5.5" - "@firebase/messaging" "0.9.6" - "@firebase/messaging-compat" "0.1.6" - "@firebase/performance" "0.5.5" - "@firebase/performance-compat" "0.1.5" - "@firebase/polyfill" "0.3.36" - "@firebase/remote-config" "0.3.4" - "@firebase/remote-config-compat" "0.1.5" - "@firebase/storage" "0.9.1" - "@firebase/storage-compat" "0.1.9" - "@firebase/util" "1.4.3" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.7: - version "3.2.9" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" - integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== - -follow-redirects@^1.0.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== - -format-util@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" - integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-monkey@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" - integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" - integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== - dependencies: - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" - integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== - dependencies: - get-intrinsic "^1.2.2" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== - dependencies: - function-bind "^1.1.2" - -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -idb@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384" - integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw== - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isbinaryfile@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -jszip@^3.10.1, jszip@^3.6.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" - integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - setimmediate "^1.0.5" - -karma-chrome-launcher@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" - integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== - dependencies: - which "^1.2.1" - -karma-mocha@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" - integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== - dependencies: - minimist "^1.2.3" - -karma-sourcemap-loader@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" - integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== - dependencies: - graceful-fs "^4.2.10" - -karma-webpack@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.0.tgz#2a2c7b80163fe7ffd1010f83f5507f95ef39f840" - integrity sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - webpack-merge "^4.1.5" - -karma@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.2.tgz#a983f874cee6f35990c4b2dcc3d274653714de8e" - integrity sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ== - dependencies: - "@colors/colors" "1.5.0" - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.4.1" - mime "^2.5.2" - minimatch "^3.0.4" - mkdirp "^0.5.5" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^4.4.1" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.30" - yargs "^16.1.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -launch-editor@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.8.1" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - -lodash@^4.17.15, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log4js@^6.4.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" - integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - flatted "^3.2.7" - rfdc "^1.3.0" - streamroller "^3.1.5" - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -long@^5.0.0: - version "5.2.3" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" - integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.3, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^0.5.5: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mocha@10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" - integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-fetch@2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" - integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-html-parser@6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.5.tgz#c819dceb13a10a7642ff92f94f870b4f77968097" - integrity sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg== - dependencies: - css-select "^5.1.0" - he "1.2.0" - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.9.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise-polyfill@8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - -protobufjs@^6.11.3: - version "6.11.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" - integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^4.0.0" - -protobufjs@^7.2.4: - version "7.2.5" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" - integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -readable-stream@^2.0.1, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^3.1.1, schema-utils@^3.1.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" - integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selenium-webdriver@4.0.0-rc-1: - version "4.0.0-rc-1" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz#b1e7e5821298c8a071e988518dd6b759f0c41281" - integrity sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw== - dependencies: - jszip "^3.6.0" - rimraf "^3.0.2" - tmp "^0.2.1" - ws ">=7.4.6" - -selenium-webdriver@^4.0.0-beta.2: - version "4.15.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.15.0.tgz#ee827f8993864dc0821df0d3b46d4f312d6f1aa3" - integrity sha512-BNG1bq+KWiBGHcJ/wULi0eKY0yaDqFIbEmtbsYJmfaEghdCkXBsx1akgOorhNwjBipOr0uwpvNXqT6/nzl+zjg== - dependencies: - jszip "^3.10.1" - tmp "^0.2.1" - ws ">=8.14.2" - -selfsigned@^2.1.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== - dependencies: - define-data-property "^1.1.1" - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -socket.io-adapter@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" - integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== - dependencies: - ws "~8.11.0" - -socket.io-parser@~4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" - integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.1" - -socket.io@^4.4.1: - version "4.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002" - integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw== - dependencies: - accepts "~1.3.4" - base64id "~2.0.0" - cors "~2.8.5" - debug "~4.3.2" - engine.io "~6.5.2" - socket.io-adapter "~2.5.2" - socket.io-parser "~4.2.4" - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-loader@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.1.tgz#72f00d05f5d1f90f80974eda781cbd7107c125f2" - integrity sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA== - dependencies: - abab "^2.0.6" - iconv-lite "^0.6.3" - source-map-js "^1.0.2" - -source-map-support@0.5.21, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -streamroller@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" - integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - fs-extra "^8.1.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.8" - -terser@^5.16.8: - version "5.24.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.24.0.tgz#4ae50302977bca4831ccc7b4fef63a3c04228364" - integrity sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tslib@^2.1.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" - integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== - -ua-parser-js@^0.7.30: - version "0.7.37" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832" - integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== - -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webextension-polyfill@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz#ccb28101c910ba8cf955f7e6a263e662d744dbb8" - integrity sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webpack-cli@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.0.tgz#abc4b1f44b50250f2632d8b8b536cfe2f6257891" - integrity sha512-a7KRJnCxejFoDpYTOwzm5o21ZXMaNqtRlvS183XzGDUPRdVEzJNImcQokqYZ8BNTnk9DkKiuWxw75+DCCoZ26w== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.0" - "@webpack-cli/info" "^2.0.1" - "@webpack-cli/serve" "^2.0.3" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@4.15.0: - version "4.15.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz#87ba9006eca53c551607ea0d663f4ae88be7af21" - integrity sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - launch-editor "^2.6.0" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.13.0" - -webpack-merge@^4.1.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.82.0: - version "5.82.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.0.tgz#3c0d074dec79401db026b4ba0fb23d6333f88e7d" - integrity sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.13.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.2" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -whatwg-fetch@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -ws@>=7.4.6, ws@>=8.14.2, ws@^8.13.0: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - -ws@~8.11.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@16.2.0, yargs@^16.1.1, yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/model/build.gradle.kts b/model/build.gradle.kts index a73825c5..b009a145 100644 --- a/model/build.gradle.kts +++ b/model/build.gradle.kts @@ -1,81 +1,38 @@ plugins { alias(libs.plugins.multiplatform) alias(libs.plugins.serialization) - alias(libs.plugins.android.library) - id ("kotlin-parcelize") apply false + id("kotlinx-atomicfu") } -val artifact = VersionCatalog.artifactName("model") -group = artifact - kotlin { jvm() - androidTarget() + iosX64() iosArm64() iosSimulatorArm64() + + linuxX64() + linuxArm64() + + mingwX64() + + macosX64() + macosArm64() + js(IR) { browser() nodejs() binaries.executable() } - jvmToolchain(CompileOptions.jvmTargetVersion) - applyDefaultHierarchyTemplate() sourceSets { - val commonMain by getting { - dependencies { - api(libs.serialization.json) - api(libs.coroutines) - api(libs.datetime) - api(libs.napier) - api(libs.skeo) - } - } - - val javaMain by creating { - dependsOn(commonMain) - - jvmMain.get().dependsOn(this) - androidMain.get().dependsOn(this) - } - - androidMain.get().apply { - apply(plugin = "kotlin-parcelize") + commonMain.dependencies { + implementation(libs.serialization) + api(libs.immutable) + implementation(libs.coroutines) + implementation(libs.tooling) } - - jvmMain.get().dependencies { - api(libs.lang) - } - - jsMain.get().dependencies { - api(libs.coroutines.js) - } - } -} - -android { - compileSdk = Configuration.compileSdk - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - - namespace = artifact - - defaultConfig { - minSdk = Configuration.minSdk - } - - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } - packaging { - resources.merges.add("META-INF/LICENSE") - resources.merges.add("META-INF/DEPENDENCIES") - resources.pickFirsts.add("**") - resources.pickFirsts.add("**/*") - resources.pickFirsts.add("*") - resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin") } } \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/BSUtil.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/BSUtil.kt index a203ed21..a2f8af7c 100644 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/BSUtil.kt +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/BSUtil.kt @@ -10,22 +10,6 @@ data object BSUtil { val episodeNumberRegex = "[|({]\\s*Ep([.]|isode)?\\s*(\\d+)\\s*[|)}]".toRegex(RegexOption.IGNORE_CASE) - fun getIntentDataUrl(data: String?): Shortcut.Intent { - val normalizedData = data?.replace("series.io", String(), true) - return when { - normalizedData.isNullOrBlank() -> Shortcut.Intent.NONE - normalizedData.contains(SEARCH, true) -> Shortcut.Intent.SEARCH - else -> { - val normalized = normalizeHref(normalizedData) - if (!normalized.equals(normalizedData, true)) { - Shortcut.Intent.Series(fixSeriesHref(normalized)) - } else { - Shortcut.Intent.NONE - } - } - } - } - fun getBurningSeriesLink(href: String, http: Boolean = false, host: String = HOST_BS_TO): String { return if (!href.matches("^\\w+?://.*".toRegex())) { if (!href.startsWith("/")) { @@ -44,77 +28,30 @@ data object BSUtil { } fun fixSeriesHref(href: String): String { - return rebuildHrefFromData(hrefDataFromHref(normalizeHref(href))) + return SeriesData.fromHref(normalizeHref(href)).toHref() } fun commonSeriesHref(href: String): String { - return rebuildHrefFromData(hrefDataFromHref(fixSeriesHref(href)).copy(second = null, third = null)) + return SeriesData.commonHref(fixSeriesHref(href)).toHref() } - fun hrefDataFromHref(href: String): Triple { - fun getTitle(): String { - val newHref = if (href.startsWith("series/")) { - href.substringAfter("series/") - } else if (href.startsWith("serie/")) { - href.substringAfter("serie/") - } else if (href.startsWith("/series/")) { - href.substringAfter("/series/") - } else if (href.startsWith("/serie/")) { - href.substringAfter("/serie/") - } else { - href - } - val potentialTitle = if (newHref.startsWith('/')) { - newHref.substringAfter('/') - } else { - newHref - } - val potTitle = potentialTitle.substringBefore('/').trim() - return if (potTitle.equals("serie", true) || potTitle.equals("series", true)) { - potentialTitle.substringAfter('/').substringBefore('/').trim() - } else { - potTitle - } - } + fun seasonFrom(href: String): Int? { + return SeriesData.fromHref(href).season + } - var newHref = normalizeHref(href) - if (newHref.startsWith('/')) { - newHref = newHref.substring(1) - } - if (newHref.startsWith("serie/", true) || newHref.startsWith("series/", true)) { - newHref = newHref.substringAfter('/') - } - val hrefSplit = newHref.split('/') - val season = if (hrefSplit.size >= 2) hrefSplit[1] else null - val language = if (hrefSplit.size >= 3) hrefSplit[2] else null - val fallbackLanguage = if (hrefSplit.size >= 4) hrefSplit[3] else null - var title = hrefSplit[0].ifBlank { - getTitle() - } - if (title.equals(season, true)) { - title = getTitle() + fun matchingUrl(url1: String?, url2: String?): String? { + val fixedUrl1 = url1?.let(::normalizeHref) + val regex = "serie\\S+".toRegex(RegexOption.IGNORE_CASE) + + if (regex.containsMatchIn(fixedUrl1 ?: "")) { + return fixedUrl1 } - return Triple( - title, - if (season.isNullOrEmpty()) null else season, - if (!fallbackLanguage.isNullOrEmpty()) { - fallbackLanguage - } else { - if (language.isNullOrEmpty()) null else language - } - ) - } - fun rebuildHrefFromData(hrefData: Triple): String { - return if (hrefData.second != null && hrefData.third != null) { - "serie/${hrefData.first}/${hrefData.second}/${hrefData.third}" - } else if (hrefData.second != null) { - "serie/${hrefData.first}/${hrefData.second}" - } else if (hrefData.third != null) { - "serie/${hrefData.first}/${hrefData.third}" - } else { - "serie/${hrefData.first}" + val fixedUrl2 = url2?.let(::normalizeHref) + if (regex.containsMatchIn(fixedUrl2 ?: "")) { + return fixedUrl2 } - } + return null + } } \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Cacheable.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Cacheable.kt deleted file mode 100644 index 1ed5c010..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Cacheable.kt +++ /dev/null @@ -1,39 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlin.time.Duration -import kotlin.time.Duration.Companion.hours - -class Cacheable(private val timeAlive: Duration = 2.hours) { - private var cachedOn: Instant? = null - private var data: T? = null - - fun cache(data: T): T { - this.data = data - this.cachedOn = Clock.System.now() - return data - } - - fun getAlive(): T? { - val cacheTime = cachedOn ?: return null - - return if (cacheTime.epochSeconds < Clock.System.now().minus(timeAlive).epochSeconds) { - null - } else { - data - } - } - - fun getUnAlive(): T? { - return data - } - - operator fun get(unAlive: Boolean = false): T? { - return if (unAlive) { - getUnAlive() - } else { - getAlive() - } - } -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/ExtensionMessage.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/ExtensionMessage.kt deleted file mode 100644 index e5727acd..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/ExtensionMessage.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ExtensionMessage( - @SerialName("set") val set: Boolean, - @SerialName("href") val href: String, - @SerialName("url") val url: String? = null -) \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreDocument.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreDocument.kt deleted file mode 100644 index ad96a7f9..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreDocument.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -@OptIn(ExperimentalSerializationApi::class) -data class FirestoreDocument( - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("name") val name: String? = null, - @SerialName("fields") val fields: Map -) \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreQuery.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreQuery.kt deleted file mode 100644 index c12846f4..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/FirestoreQuery.kt +++ /dev/null @@ -1,91 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -@OptIn(ExperimentalSerializationApi::class) -data class FirestoreQuery( - @SerialName("structuredQuery") val structuredQuery: StructuredQuery -) { - @Serializable - @OptIn(ExperimentalSerializationApi::class) - data class Value( - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("stringValue") val stringValue: String? = null, - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("booleanValue") val booleanValue: Boolean? = null, - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("integerValue") val integerValue: Int? = null, - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("doubleValue") val doubleValue: Double? = null, - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("arrayValue") val arrayValue: ArrayValue? = null - ) { - @Serializable - data class ArrayValue( - @SerialName("values") val values: List - ) - } - - @Serializable - @OptIn(ExperimentalSerializationApi::class) - data class StructuredQuery( - @SerialName("from") val from: List, - @SerialName("where") val where: Where, - @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("select") val select: Select? = null - ) { - - @Serializable - data class From( - @SerialName("collectionId") val collectionId: String, - @SerialName("allDescendants") val allDescendants: Boolean = false - ) - - @Serializable - data class Where( - @SerialName("fieldFilter") val fieldFilter: FieldFilter - ) { - - @Serializable - data class FieldFilter internal constructor( - @SerialName("field") val field: Field, - @SerialName("op") val op: String, - @SerialName("value") val value: Value - ) { - - constructor( - field: Field, - op: OP, - value: Value - ) : this(field, op.name, value) - - sealed class OP(val name: String) { - data object OPERATOR_UNSPECIFIED : OP("OPERATOR_UNSPECIFIED") - data object LESS_THAN : OP("LESS_THAN") - data object LESS_THAN_OR_EQUAL : OP("LESS_THAN_OR_EQUAL") - data object GREATER_THAN : OP("GREATER_THAN") - data object GREATER_THAN_OR_EQUAL : OP("GREATER_THAN_OR_EQUAL") - data object EQUAL : OP("EQUAL") - data object NOT_EQUAL : OP("NOT_EQUAL") - data object ARRAY_CONTAINS : OP("ARRAY_CONTAINS") - data object IN : OP("IN") - data object ARRAY_CONTAINS_ANY : OP("ARRAY_CONTAINS_ANY") - data object NOT_IN : OP("NOT_IN") - } - } - } - - @Serializable - data class Select( - @SerialName("fields") val fields: List - ) - - @Serializable - data class Field( - @SerialName("fieldPath") val fieldPath: String - ) - } - - @Serializable - data class Response( - @SerialName("document") val document: FirestoreDocument - ) -} diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Genre.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Genre.kt deleted file mode 100644 index 036335f5..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Genre.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Genre( - @SerialName("title") val title: String, - @SerialName("items") val items: List -) { - - @Serializable - data class Item( - @SerialName("title") override val title: String, - @SerialName("href") val href: String - ) : TitleHolder() -} diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Home.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Home.kt index 18008fa0..e988e6b7 100644 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Home.kt +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Home.kt @@ -1,24 +1,26 @@ package dev.datlag.burningseries.model +import dev.datlag.burningseries.model.serializer.SerializableImmutableList +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import kotlinx.collections.immutable.persistentSetOf import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @Serializable data class Home( - @SerialName("latestEpisodes") val episodes: List = emptyList(), - @SerialName("latestSeries") val series: List = emptyList() + @SerialName("latestEpisodes") val episodes: SerializableImmutableSet = persistentSetOf(), + @SerialName("latestSeries") val series: SerializableImmutableSet = persistentSetOf() ) { @Serializable data class Episode( @SerialName("title") val fullTitle: String, - @SerialName("href") val href: String, - @SerialName("infoText") val info: String = String(), - @SerialName("infoFlags") val flags: List = emptyList(), - @SerialName("isNsfw") val isNsfw: Boolean = false, - @SerialName("cover") val coverHref: String? = null - ) : TitleHolder() { + @SerialName("href") override val href: String, + @SerialName("infoText") val info: String? = null, + @SerialName("infoFlags") val flags: SerializableImmutableSet = persistentSetOf(), + @SerialName("cover") override val coverHref: String? = null + ) : SeriesData() { @Transient private val seriesAndEpisodeMatch = Regex( @@ -32,26 +34,31 @@ data class Home( @Transient val episode: String? = seriesAndEpisodeMatch?.groupValues?.get(3)?.trim()?.ifBlank { null } - @Transient - override val title: String = series ?: fullTitle + override val title: String + get() = series ?: fullTitle + + override val mainTitle: String + get() = series ?: super.mainTitle + + override val subTitle: String? + get() = episode ?: super.subTitle @Serializable data class Flag( @SerialName("class") val clazz: String, - @SerialName("title") val title: String + @SerialName("title") val title: String? ) { @Transient val bestCountryCode: String? = clazz.split(" ").firstOrNull { !it.equals("flag", true) && it.contains("flag", true) - }?.replace("flag", String(), true)?.replace("-", String(), true) + }?.replace("flag", "", true)?.replace("-", "", true) } } @Serializable data class Series( @SerialName("title") override val title: String, - @SerialName("href") val href: String, - @SerialName("isNsfw") val isNsfw: Boolean = false, - @SerialName("coverHref") val coverHref: String? = null - ) : TitleHolder() -} \ No newline at end of file + @SerialName("href") override val href: String, + @SerialName("cover") override val coverHref: String? = null + ) : SeriesData() +} diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/HosterScraping.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/HosterScraping.kt index d18f3b5c..fbfccaa4 100644 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/HosterScraping.kt +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/HosterScraping.kt @@ -9,19 +9,14 @@ data class HosterScraping( @SerialName("url") val url: String, @SerialName("embed") val embed: Boolean = false ) { - val firestore = Firestore( + + val fireStore = FireStore( id = href, url = url ) - val jsonBase = JsonBaseCaptchaEntry( - url = url, - embed = embed, - broken = false - ) - @Serializable - data class Firestore( + data class FireStore( @SerialName("id") val id: String, @SerialName("url") val url: String ) diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/JsonBaseCaptchaEntry.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/JsonBaseCaptchaEntry.kt deleted file mode 100644 index f4199937..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/JsonBaseCaptchaEntry.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class JsonBaseCaptchaEntry( - @SerialName("url") val url: String, - @SerialName("embed") val embed: Boolean, - @SerialName("broken") val broken: Boolean = false -) diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Release.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Release.kt deleted file mode 100644 index baab7a65..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Release.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.datlag.burningseries.model - -import dev.datlag.burningseries.model.common.getDigitsOrNull -import dev.datlag.burningseries.model.common.scopeCatching -import kotlinx.datetime.toInstant -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient - -@Serializable -data class Release( - @SerialName("url") val url: String, - @SerialName("html_url") val htmlUrl: String = url, - @SerialName("tag_name") val tagName: String, - @SerialName("name") val title: String, - @SerialName("draft") val draft: Boolean = false, - @SerialName("prerelease") val preRelease: Boolean = false, - @SerialName("published_at") val publishedAt: String? = null, - @Transient val publishedAtSeconds: Long = scopeCatching { publishedAt?.toInstant()?.epochSeconds }.getOrNull() ?: 0L -) { - - @Transient - val tagAsNumber: String? = tagName.getDigitsOrNull() -} diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SearchItem.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SearchItem.kt new file mode 100644 index 00000000..d3110b0f --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SearchItem.kt @@ -0,0 +1,17 @@ +package dev.datlag.burningseries.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class SearchItem( + @SerialName("title") override val title: String, + @SerialName("href") override val href: String, + @SerialName("cover") override val coverHref: String? = null, + @SerialName("genre") val genre: String? = null +) : SeriesData() { + + @Transient + val isAnime: Boolean = genre?.contains("Anime", ignoreCase = true) == true +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Series.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Series.kt index d15e56b6..8f76072f 100644 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Series.kt +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Series.kt @@ -1,6 +1,9 @@ package dev.datlag.burningseries.model -import dev.datlag.burningseries.model.common.getDigitsOrNull +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import dev.datlag.tooling.getDigitsOrNull +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -9,33 +12,44 @@ import kotlinx.serialization.Transient data class Series( @SerialName("title") override val title: String, @SerialName("description") val description: String, - @SerialName("cover") val coverHref: String? = null, - @SerialName("href") val href: String, + @SerialName("cover") override val coverHref: String? = null, + @SerialName("href") override val href: String, @SerialName("seasonTitle") val seasonTitle: String, @SerialName("selectedLanguage") val selectedLanguage: String?, - @SerialName("seasons") val seasons: List, - @SerialName("languages") val languages: List, - @SerialName("infos") val infos: List = emptyList(), - @SerialName("episodes") val episodes: List -) : TitleHolder() { + @SerialName("seasons") val seasons: SerializableImmutableSet = persistentSetOf(), + @SerialName("languages") val languages: SerializableImmutableSet = persistentSetOf(), + @SerialName("info") val info: SerializableImmutableSet = persistentSetOf(), + @SerialName("episodes") val episodes: SerializableImmutableSet = persistentSetOf() +) : SeriesData() { @Transient - val isAnime: Boolean = infos.filter { + val genres = info.filter { it.header.equals("Genre", ignoreCase = true) || it.header.equals("Genres", ignoreCase = true) - }.any { + }.toImmutableSet() + + @Transient + val firstGenre = genres.firstNotNullOfOrNull { it.data.ifBlank { null } } + + @Transient + val isAnime: Boolean = genres.any { it.data.contains("Anime", ignoreCase = true) } + @Transient + val infoWithoutGenre = info.filterNot { + it.header.equals("Genre", ignoreCase = true) || it.header.equals("Genres", ignoreCase = true) + }.toImmutableSet() + val currentSeason: Season? by lazy { seasons.firstOrNull { - it.title.equals(seasonTitle, true) + it.value == season } ?: seasons.firstOrNull { - it.title.trim().equals(seasonTitle.trim(), true) + it.title.equals(seasonTitle, true) } ?: seasons.firstOrNull { it.title.equals(seasonTitle.toIntOrNull()?.toString(), true) } ?: seasons.firstOrNull { val titleInt = it.title.toIntOrNull() - val seasonInt = seasonTitle.toIntOrNull() + val seasonInt = season titleInt != null && seasonInt != null && titleInt == seasonInt } ?: seasons.firstOrNull { @@ -48,28 +62,13 @@ data class Series( } val currentLanguage: Language? by lazy { - languages.find { + languages.firstOrNull { + it.value.equals(language, true) + } ?: languages.firstOrNull { it.value.equals(selectedLanguage, true) - || it.value.trim().equals(selectedLanguage?.trim(), true) } } - fun hrefBuilder(season: Int? = currentSeason?.value, language: String? = currentLanguage?.value ?: selectedLanguage): String { - val hrefData = BSUtil.hrefDataFromHref( - BSUtil.normalizeHref(href) - ) - - return BSUtil.fixSeriesHref( - BSUtil.rebuildHrefFromData( - Triple( - first = hrefData.first, - second = season?.toString() ?: hrefData.second, - third = language ?: hrefData.third - ) - ) - ) - } - @Serializable data class Season( @SerialName("value") val value: Int, @@ -84,22 +83,28 @@ data class Series( @Serializable data class Info( - @SerialName("header") val header: String = String(), - @SerialName("data") val data: String = String(), + @SerialName("header") val header: String = "", + @SerialName("data") val data: String = "", ) @Serializable data class Episode( @SerialName("number") val number: String, - @SerialName("title") val title: String, - @SerialName("href") val href: String, - @SerialName("hosters") val hosters: List - ) { + @SerialName("title") val fullTitle: String, + @SerialName("href") override val href: String, + @SerialName("hoster") val hoster: SerializableImmutableSet + ) : SeriesData() { + + override val coverHref: String? = null + + @Transient + val episodeNumber: String = BSUtil.episodeNumberRegex.find(fullTitle)?.groupValues?.lastOrNull() ?: number - val episodeNumber: String = BSUtil.episodeNumberRegex.find(title)?.groupValues?.lastOrNull() ?: number - val episodeTitle: String = BSUtil.episodeNumberRegex.replaceFirst(title, String()).trim().ifBlank { title } + @Transient + override val title: String = BSUtil.episodeNumberRegex.replaceFirst(fullTitle, String()).trim().ifBlank { fullTitle } - val convertedNumber: Int? = number.toIntOrNull() ?: number.getDigitsOrNull()?.toIntOrNull() + @Transient + val convertedNumber: Int? = episodeNumber.toIntOrNull() ?: episodeNumber.getDigitsOrNull()?.toIntOrNull() @Serializable data class Hoster( diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SeriesData.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SeriesData.kt new file mode 100644 index 00000000..365fddd2 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/SeriesData.kt @@ -0,0 +1,180 @@ +package dev.datlag.burningseries.model + +import dev.datlag.burningseries.model.algorithm.JaroWinkler +import dev.datlag.burningseries.model.common.moreThan +import dev.datlag.burningseries.model.serializer.SerializableImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmOverloads + +@Serializable +abstract class SeriesData { + abstract val href: String + abstract val title: String + abstract val coverHref: String? + + private val defaultValues by lazy { + createDefaultValues() + } + + val source: String by lazy { + defaultValues.first + } + open val season: Int? by lazy { + defaultValues.second + } + open val language: String? by lazy { + defaultValues.third + } + + val allTitles by lazy { + val pipeSplit = title.split('|').filterNot { + it.isBlank() + }.map { + it.trim() + }.toImmutableList() + + if (pipeSplit.size == 1) { + if (title.moreThan(':', 1)) { + pipeSplit + } else { + title.split(':').filterNot { it.isBlank() }.map { it.trim() }.toImmutableList() + } + } else { + pipeSplit + } + } + + val bestTitle by lazy { + when { + allTitles.size <= 1 -> allTitles.firstOrNull() ?: title + else -> { + val newTitles = mutableListOf() + allTitles.forEach { str -> + val strFlatten = str.replace("\\s".toRegex(RegexOption.MULTILINE), String()).trim() + + if (newTitles.none { + JaroWinkler.distance(str, it) > 0.95 || run { + val itFlatten = it.replace("\\s".toRegex(RegexOption.MULTILINE), String()).trim() + + JaroWinkler.distance(strFlatten, itFlatten) > 0.95 + } + }) { + newTitles.add(str) + } + } + newTitles.toSet().joinToString(separator = " | ") + } + } + } + + open val mainTitle by lazy { + bestTitle.substringBefore('|').trim() + } + + open val subTitle by lazy { + bestTitle.substringAfter('|', missingDelimiterValue = "").trim().ifBlank { null } + } + + val hasSubtitle by lazy { + !subTitle.isNullOrBlank() + } + + private fun getHrefTitle(): String { + val newHref = if (href.startsWith("series/")) { + href.substringAfter("series/") + } else if (href.startsWith("serie/")) { + href.substringAfter("serie/") + } else if (href.startsWith("/series/")) { + href.substringAfter("/series/") + } else if (href.startsWith("/serie/")) { + href.substringAfter("/serie/") + } else { + href + } + val potentialTitle = if (newHref.startsWith('/')) { + newHref.substringAfter('/') + } else { + newHref + } + val potTitle = potentialTitle.substringBefore('/').trim() + return if (potTitle.equals("serie", true) || potTitle.equals("series", true)) { + potentialTitle.substringAfter('/').substringBefore('/').trim() + } else { + potTitle + } + } + + private fun createDefaultValues(): Triple { + var newHref = BSUtil.normalizeHref(href) + if (newHref.startsWith('/')) { + newHref = newHref.substring(1) + } + if (newHref.startsWith("serie/", true) || newHref.startsWith("series/", true)) { + newHref = newHref.substringAfter('/') + } + val hrefSplit = newHref.split('/') + val season = if (hrefSplit.size >= 2) hrefSplit[1] else null + val convertedSeason = season?.ifBlank { null }?.trim()?.toIntOrNull() + val missingSeasonLanguage = if (convertedSeason == null && hrefSplit.size == 2) hrefSplit[1] else null + val language = if (hrefSplit.size >= 3) hrefSplit[2] else null + val fallbackLanguage = if (hrefSplit.size >= 4) hrefSplit[3] else null + var title = hrefSplit[0].ifBlank { + getHrefTitle() + } + if (title.equals(season, true)) { + title = getHrefTitle() + } + + return Triple( + first = title, + second = convertedSeason, + third = if (!fallbackLanguage.isNullOrBlank()) { + fallbackLanguage + } else { + if (language.isNullOrBlank()) missingSeasonLanguage else language + } + ) + } + + @JvmOverloads + fun toHref(newSeason: Int? = season, newLanguage: String? = language): String { + return if (newSeason != null && newLanguage != null) { + "serie/${source}/${newSeason}/${newLanguage}" + } else if (newSeason != null) { + "serie/${source}/${newSeason}" + } else if (newLanguage != null) { + "serie/${source}/${newLanguage}" + } else { + "serie/$source" + } + } + + fun shadowCopy( + href: String = this.href, + title: String = this.title, + coverHref: String? = this.coverHref, + ) = object : SeriesData() { + override val href: String = href + override val title: String = title + override val coverHref: String? = coverHref + } + + companion object { + fun fromHref(href: String) = object : SeriesData() { + override val href: String = href + override val title: String = "" + override val coverHref: String? = null + } + + fun commonHref(href: String) = object : SeriesData() { + override val href: String = href + override val title: String = "" + override val coverHref: String? = null + + override val season: Int? = null + override val language: String? = null + } + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Shortcut.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Shortcut.kt deleted file mode 100644 index c9a9dfc2..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/Shortcut.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface Shortcut { - - @Serializable - sealed interface Intent : Shortcut { - - @Serializable - data object SEARCH : Intent - - @Serializable - data class Series(val href: String) : Intent - - @Serializable - data object NONE : Intent - } -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/TitleHolder.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/TitleHolder.kt deleted file mode 100644 index fd841987..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/TitleHolder.kt +++ /dev/null @@ -1,45 +0,0 @@ -package dev.datlag.burningseries.model - -import dev.datlag.burningseries.model.algorithm.JaroWinkler - -/** - * Cannot be applied to Database Series object - */ -abstract class TitleHolder { - abstract val title: String - - val allTitles by lazy { - title.split('|').filterNot { it.isBlank() }.map { it.trim() }.distinct() - } - - val bestTitle by lazy { - when { - allTitles.size <= 1 -> allTitles.firstOrNull() ?: title - else -> { - val newTitles = mutableListOf() - allTitles.forEach { str -> - val strFlatten = str.replace("\\s".toRegex(RegexOption.MULTILINE), String()).trim() - - if (newTitles.none { - JaroWinkler.distance(str, it) > 0.95 || run { - val itFlatten = it.replace("\\s".toRegex(RegexOption.MULTILINE), String()).trim() - - JaroWinkler.distance(strFlatten, itFlatten) > 0.95 - } - }) { - newTitles.add(str) - } - } - newTitles.toSet().joinToString(separator = " | ") - } - } - } - - val mainTitle by lazy { - bestTitle.substringBefore('|').trim() - } - - val subTitle by lazy { - bestTitle.substringAfter('|', missingDelimiterValue = "").trim().ifBlank { null } - } -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/WrapAPIResponse.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/WrapAPIResponse.kt deleted file mode 100644 index a04feabe..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/WrapAPIResponse.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.datlag.burningseries.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - -@Serializable -data class WrapAPIResponse( - @SerialName("success") val success: Boolean, - @SerialName("data") val data: JsonElement -) diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/algorithm/MD5.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/algorithm/MD5.kt deleted file mode 100644 index d0214a1c..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/algorithm/MD5.kt +++ /dev/null @@ -1,118 +0,0 @@ -package dev.datlag.burningseries.model.algorithm - -import kotlin.math.abs -import kotlin.math.sin - -data object MD5 { - - private val INIT_A = 0x67452301 - private val INIT_B = 0xEFCDAB89L.toInt() - private val INIT_C = 0x98BADCFEL.toInt() - private val INIT_D = 0x10325476 - - private val SHIFT_AMTS = intArrayOf( - 7, 12, 17, 22, - 5, 9, 14, 20, - 4, 11, 16, 23, - 6, 10, 15, 21 - ) - - private val TABLE_T = IntArray(64) { - ((1L shl 32) * abs(sin(it + 1.0))).toLong().toInt() - } - - fun compute(message: ByteArray): ByteArray { - val messageLenBytes = message.size - val numBlocks = ((messageLenBytes + 8) ushr 6) + 1 - val totalLen = numBlocks shl 6 - val paddingBytes = ByteArray(totalLen - messageLenBytes) - paddingBytes[0] = 0x80.toByte() - var messageLenBits = (messageLenBytes shl 3).toLong() - - for (i in 0..7) { - paddingBytes[paddingBytes.size - 8 + i] = messageLenBits.toByte() - messageLenBits = messageLenBits ushr 8 - } - - var a = INIT_A - var b = INIT_B - var c = INIT_C - var d = INIT_D - val buffer = IntArray(16) - - for (i in 0 until numBlocks) { - var index = i shl 6 - - for (j in 0..63) { - val temp = if (index < messageLenBytes) message[index] else - paddingBytes[index - messageLenBytes] - buffer[j ushr 2] = (temp.toInt() shl 24) or (buffer[j ushr 2] ushr 8) - index++ - } - - val originalA = a - val originalB = b - val originalC = c - val originalD = d - - for (j in 0..63) { - val div16 = j ushr 4 - var f = 0 - var bufferIndex = j - when (div16) { - 0 -> { - f = (b and c) or (b.inv() and d) - } - - 1 -> { - f = (b and d) or (c and d.inv()) - bufferIndex = (bufferIndex * 5 + 1) and 0x0F - } - - 2 -> { - f = b xor c xor d; - bufferIndex = (bufferIndex * 3 + 5) and 0x0F - } - - 3 -> { - f = c xor (b or d.inv()); - bufferIndex = (bufferIndex * 7) and 0x0F - } - } - - val temp = b + (a + f + buffer[bufferIndex] + TABLE_T[j]).rotateLeft(SHIFT_AMTS[(div16 shl 2) or (j and 3)]) - a = d - d = c - c = b - b = temp - } - - a += originalA - b += originalB - c += originalC - d += originalD - } - - val md5 = ByteArray(16) - var count = 0 - - for (i in 0..3) { - var n = if (i == 0) a else (if (i == 1) b else (if (i == 2) c else d)) - - for (j in 0..3) { - md5[count++] = n.toByte() - n = n ushr 8 - } - } - return md5 - } - - @OptIn(ExperimentalStdlibApi::class) - fun hexString(byteArray: ByteArray): String { - return compute(byteArray).toHexString() - } - - fun hexString(value: String): String { - return hexString(value.encodeToByteArray()) - } -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.kt similarity index 100% rename from model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.kt rename to model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.kt diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendBoolean.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendBoolean.kt new file mode 100644 index 00000000..1c5a1d45 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendBoolean.kt @@ -0,0 +1,3 @@ +package dev.datlag.burningseries.model.common + +fun Boolean.toInt() = if (this) 1 else 0 \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCollection.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCollection.kt deleted file mode 100644 index 7afe1a33..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCollection.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.datlag.burningseries.model.common - -import kotlin.math.max -import kotlin.math.min - -fun listFrom(vararg list: Collection): List { - return mutableListOf().apply { - list.forEach(::addAll) - } -} - -fun setFrom(vararg list: Collection): Set { - return mutableSetOf().apply { - list.forEach(::addAll) - } -} - -fun T.asList(): List { - return listOf(this) -} - -fun List.safeSubList(from: Int, to: Int): List { - if (this.isEmpty()) { - return this - } - - val safeFrom = max(min(from, lastIndex), 0) - return this.subList( - safeFrom, - max(safeFrom, min(to, size)) - ) -} - -fun Collection.contains(element: String, ignoreCase: Boolean): Boolean { - return if (ignoreCase) { - this.any { it.equals(element, true) } - } else { - this.contains(element) - } -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCoroutine.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCoroutine.kt deleted file mode 100644 index 043aff65..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendCoroutine.kt +++ /dev/null @@ -1,79 +0,0 @@ -package dev.datlag.burningseries.model.common - -import dev.datlag.burningseries.model.state.CatchResult -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.firstOrNull - -fun scopeCatching(block: () -> T): Result = try { - Result.success(block()) -} catch (e: Throwable) { - if (e is CancellationException) { - throw e - } - Result.failure(e) -} - -suspend fun suspendCatching(block: suspend CoroutineScope.() -> T): Result = coroutineScope { - try { - Result.success( - block(this) - ) - } catch (e: Throwable) { - if (e is CancellationException) { - throw e - } - Result.failure(e) - } -} - -suspend fun suspendCatchResult(block: suspend CoroutineScope.() -> T): CatchResult = coroutineScope { - val result = suspendCatching(block) - return@coroutineScope if (result.isFailure) { - CatchResult.Error(result.exceptionOrNull()) - } else { - result.getOrNull()?.let { - CatchResult.Success(it) - } ?: CatchResult.Error(result.exceptionOrNull()) - } -} - -fun safeCast(block: () -> T?): T? { - return scopeCatching { - block() - }.getOrNull() -} - -suspend fun suspendSafeCast(block: () -> T?): T? { - return suspendCatching { - block() - }.getOrNull() -} - -inline fun Any?.safeCast(): T? { - return safeCast { - if (this is T) { - this - } else { - this as? T? - } - } -} - -suspend fun Flow.collectSafe(collector: FlowCollector) { - suspendCatching { - this@collectSafe.collect(collector) - }.getOrNull() ?: suspendCatching { - this@collectSafe.firstOrNull() - }.getOrNull()?.let { collector.emit(it) } -} - -suspend fun StateFlow.collectSafe(collector: FlowCollector) { - suspendCatching { - this@collectSafe.collect(collector) - }.getOrNull() ?: collector.emit(this.value) -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendString.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendString.kt index d360e163..cd80fa10 100644 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendString.kt +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/common/ExtendString.kt @@ -1,8 +1,24 @@ package dev.datlag.burningseries.model.common -fun String.getDigitsOrNull(): String? { - val replaced = this.replace("\\D+".toRegex(), String()) - return replaced.ifBlank { - null +fun String.occurrences(char: Char): Int { + var counter = 0 + for (c in this) { + if (c == char) { + counter++ + } } + return counter +} + +fun String.moreThan(char: Char, search: Int): Boolean { + var counter = 0 + for (c in this) { + if (c == char) { + counter++ + if (counter > search) { + return true + } + } + } + return counter > search } \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/ConflatedExecutor.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/ConflatedExecutor.kt new file mode 100644 index 00000000..4bff9ae6 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/ConflatedExecutor.kt @@ -0,0 +1,35 @@ +package dev.datlag.burningseries.model.coroutines + +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.* + +interface ConflatedExecutor { + suspend fun conflate(block: suspend () -> T): T +} + +internal class ConflatedExecutorImpl : ConflatedExecutor { + private val activeTask: AtomicRef = atomic(null) + + suspend override fun conflate(block: suspend () -> T): T { + return coroutineScope { + val newTask = async(start = CoroutineStart.LAZY) { block() }.also { task -> + task.invokeOnCompletion { + activeTask.compareAndSet(task, null) + } + } + + val result: T + while (true) { + if (!activeTask.compareAndSet(null, newTask)) { + activeTask.value?.cancelAndJoin() + yield() + } else { + result = newTask.await() + break + } + } + return@coroutineScope result + } + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/Executor.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/Executor.kt new file mode 100644 index 00000000..35726d07 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/Executor.kt @@ -0,0 +1,24 @@ +package dev.datlag.burningseries.model.coroutines + +import kotlin.jvm.JvmOverloads + +class Executor @JvmOverloads constructor( + private val conflatedExecutor: ConflatedExecutor = ConflatedExecutorImpl(), + private val queueExecutor: QueueExecutor = QueueExecutorImpl() +) : ConflatedExecutor by conflatedExecutor, QueueExecutor by queueExecutor { + + suspend fun execute( + schema: Schema, + block: suspend () -> T + ): T { + return when (schema) { + is Schema.Conflated -> conflate(block) + is Schema.Queue -> enqueue(block) + } + } + + sealed interface Schema { + data object Conflated : Schema + data object Queue: Schema + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/QueueExecutor.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/QueueExecutor.kt new file mode 100644 index 00000000..10aae2e2 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/coroutines/QueueExecutor.kt @@ -0,0 +1,18 @@ +package dev.datlag.burningseries.model.coroutines + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +interface QueueExecutor { + suspend fun enqueue(block: suspend () -> T): T +} + +internal class QueueExecutorImpl : QueueExecutor { + private val mutex = Mutex() + + override suspend fun enqueue(block: suspend () -> T): T { + mutex.withLock { + return block() + } + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableListSerializer.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableListSerializer.kt new file mode 100644 index 00000000..e758ddd6 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableListSerializer.kt @@ -0,0 +1,32 @@ +package dev.datlag.burningseries.model.serializer + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.serialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +typealias SerializableImmutableList = @Serializable(ImmutableListSerializer::class) ImmutableList + +@Serializer(forClass = ImmutableList::class) +class ImmutableListSerializer(private val dataSerializer: KSerializer) : KSerializer> { + private class PersistentListDescriptor : SerialDescriptor by serialDescriptor>() { + @ExperimentalSerializationApi + override val serialName: String = "kotlinx.serialization.immutable.ImmutableList" + } + + override val descriptor: SerialDescriptor = PersistentListDescriptor() + override fun serialize(encoder: Encoder, value: ImmutableList) { + return ListSerializer(dataSerializer).serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): ImmutableList { + return ListSerializer(dataSerializer).deserialize(decoder).toPersistentList() + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableSetSerializer.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableSetSerializer.kt new file mode 100644 index 00000000..70cc4b99 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/serializer/ImmutableSetSerializer.kt @@ -0,0 +1,32 @@ +package dev.datlag.burningseries.model.serializer + +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.toPersistentSet +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.serialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +typealias SerializableImmutableSet = @Serializable(ImmutableSetSerializer::class) ImmutableSet + +@Serializer(forClass = ImmutableSet::class) +class ImmutableSetSerializer(private val dataSerializer: KSerializer) : KSerializer> { + private class PersistentListDescriptor : SerialDescriptor by serialDescriptor>() { + @ExperimentalSerializationApi + override val serialName: String = "kotlinx.serialization.immutable.ImmutableSet" + } + + override val descriptor: SerialDescriptor = PersistentListDescriptor() + override fun serialize(encoder: Encoder, value: ImmutableSet) { + return SetSerializer(dataSerializer).serialize(encoder, value) + } + + override fun deserialize(decoder: Decoder): ImmutableSet { + return SetSerializer(dataSerializer).deserialize(decoder).toPersistentSet() + } +} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/CatchResult.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/CatchResult.kt deleted file mode 100644 index 60514655..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/CatchResult.kt +++ /dev/null @@ -1,52 +0,0 @@ -package dev.datlag.burningseries.model.state - - -sealed interface CatchResult { - - val isSuccess: Boolean - get() = this is Success - - val isError: Boolean - get() = this is Error - - fun onError(callback: (Throwable?) -> Unit) = apply { - if (this is Error) { - callback(this.throwable) - } - } - - fun onSuccess(callback: (T & Any) -> Unit) = apply { - if (this is Success) { - callback(this.data) - } - } - - fun asSuccess(onError: () -> T & Any): T & Any { - return if (this is Success) { - this.data - } else { - onError() - } - } - - fun asNullableSuccess(onError: () -> T? = { null }): T? { - return if (this is Success) { - this.data - } else { - onError() - } - } - - fun asError(onSuccess: () -> Throwable? = { null }): Throwable? { - return if (this is Error) { - this.throwable - } else { - onSuccess() - } - } - - data class Success( - val data: T & Any - ) : CatchResult - data class Error(val throwable: Throwable?) : CatchResult -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/EpisodeState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/EpisodeState.kt deleted file mode 100644 index a2b55a03..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/EpisodeState.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.Series -import dev.datlag.skeo.Stream - -sealed interface EpisodeState { - data object Waiting : EpisodeState - data class Loading(override val episode: Series.Episode) : EpisodeState, EpisodeHolder - data class SuccessHoster(override val episode: Series.Episode, val results: Collection) : EpisodeState, EpisodeHolder - data class SuccessStream(override val episode: Series.Episode, val results: Collection) : EpisodeState, EpisodeHolder - data class ErrorHoster(override val episode: Series.Episode) : EpisodeState, EpisodeHolder - data class ErrorStream(override val episode: Series.Episode) : EpisodeState, EpisodeHolder - - interface EpisodeHolder { - val episode: Series.Episode - } -} - -sealed interface EpisodeAction { - data class Load(val episode: Series.Episode) : EpisodeAction -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/HomeState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/HomeState.kt deleted file mode 100644 index 3347dc77..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/HomeState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.Home - -sealed interface HomeState { - data object Loading : HomeState - data class Success(val home: Home, val onDeviceReachable: Boolean) : HomeState - data object Error : HomeState -} - -sealed interface HomeAction { - data object Retry : HomeAction -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/ReleaseState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/ReleaseState.kt deleted file mode 100644 index 2e461a00..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/ReleaseState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.Release - -sealed interface ReleaseState { - data object Loading : ReleaseState - data class Success(val releases: List) : ReleaseState - data object Error : ReleaseState -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SaveState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SaveState.kt deleted file mode 100644 index cfac6970..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SaveState.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.skeo.Stream - -sealed interface SaveState { - data object Waiting : SaveState - data class Saving(val data: HosterScraping, val loadStream: Boolean) : SaveState - data class Success(val stream: Stream?) : SaveState - data class Error(val stream: Stream?) : SaveState -} - -sealed interface SaveAction { - data class Save(val data: HosterScraping, val loadStream: Boolean = true) : SaveAction -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SearchState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SearchState.kt deleted file mode 100644 index 3d31b00b..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SearchState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.Genre - -sealed interface SearchState { - data object Loading : SearchState - data class Success(val genres: List) : SearchState - data object Error : SearchState -} - -sealed interface SearchAction { - data object Retry : SearchAction -} \ No newline at end of file diff --git a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SeriesState.kt b/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SeriesState.kt deleted file mode 100644 index c20da763..00000000 --- a/model/src/commonMain/kotlin/dev/datlag/burningseries/model/state/SeriesState.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.datlag.burningseries.model.state - -import dev.datlag.burningseries.model.Series - -sealed interface SeriesState { - - val href: String - - data class Loading(override val href: String) : SeriesState - data class Success(val series: Series, val onDeviceReachable: Boolean) : SeriesState { - override val href: String = series.href - } - data class Error(override val href: String) : SeriesState -} - -sealed interface SeriesAction { - data object Retry : SeriesAction - data class Load(val href: String) : SeriesAction -} \ No newline at end of file diff --git a/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendFile.kt b/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendFile.kt deleted file mode 100644 index ea68d3d3..00000000 --- a/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendFile.kt +++ /dev/null @@ -1,215 +0,0 @@ -package dev.datlag.burningseries.model.common - -import java.io.File -import java.io.RandomAccessFile -import java.nio.channels.FileChannel -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.LinkOption -import java.util.stream.Collectors - -fun File.openReadChannel(): FileChannel { - val reader = RandomAccessFile(this, "r") - return reader.channel -} - -fun File.openWriteChannel(): FileChannel { - val writer = RandomAccessFile(this, "rw") - return writer.channel -} - -fun File?.existsSafely(): Boolean { - if (this == null) { - return false - } - - return scopeCatching { - Files.exists(this.toPath()) - }.getOrNull() ?: scopeCatching { - this.exists() - }.getOrNull() ?: false -} - -fun File.canReadSafely(): Boolean { - return scopeCatching { - Files.isReadable(this.toPath()) - }.getOrNull() ?: scopeCatching { - this.canRead() - }.getOrNull() ?: false -} - -fun File.canWriteSafely(): Boolean { - return scopeCatching { - Files.isWritable(this.toPath()) - }.getOrNull() ?: scopeCatching { - this.canWrite() - }.getOrNull() ?: false -} - -fun File?.existsRSafely(): Boolean { - if (this == null) { - return false - } - - return existsSafely() && canReadSafely() -} - -fun File?.existsRWSafely(): Boolean { - if (this == null) { - return false - } - - return existsSafely() && canReadSafely() && canWriteSafely() -} - -fun File.isSymlinkSafely(): Boolean { - return scopeCatching { - Files.isSymbolicLink(this.toPath()) - }.getOrNull() ?: scopeCatching { - !Files.isRegularFile(this.toPath(), LinkOption.NOFOLLOW_LINKS) - }.getOrNull() ?: false -} - -fun File.getRealFile(): File { - return if (isSymlinkSafely()) scopeCatching { - Files.readSymbolicLink(this.toPath()).toFile() - }.getOrNull() ?: this else this -} - -fun File.isSame(file: File?): Boolean { - var sourceFile = this.getRealFile() - if (!sourceFile.existsSafely()) { - sourceFile = this - } - - var targetFile = file?.getRealFile() ?: file - if (!targetFile.existsSafely()) { - targetFile = file - } - - return if (targetFile == null) { - false - } else { - this == targetFile || scopeCatching { - sourceFile.absoluteFile == targetFile.absoluteFile || Files.isSameFile(sourceFile.toPath(), targetFile.toPath()) - }.getOrNull() ?: false - } -} - -fun Collection.normalize(): List { - val list: MutableList = mutableListOf() - this.forEach { file -> - var realFile = file.getRealFile() - if (!realFile.existsSafely()) { - if (file.existsSafely()) { - realFile = file - } else { - return@forEach - } - } - if (list.firstOrNull { it.isSame(realFile) } == null) { - list.add(realFile) - } - } - return list -} - -fun File.listFilesSafely(): List { - return scopeCatching { - this.listFiles() - }.getOrNull()?.filterNotNull() ?: scopeCatching { - Files.list(this.toPath()).collect(Collectors.toList()).mapNotNull { path -> - path?.toFile() - } - }.getOrNull() ?: emptyList() -} - -fun File.mkdirsSafely(): Boolean = scopeCatching { - this.mkdirs() -}.getOrNull() ?: false - -fun File.deleteSafely(): Boolean { - return scopeCatching { - Files.delete(this.toPath()) - }.isSuccess || scopeCatching { - this.delete() - }.getOrNull() ?: false -} - -fun File.move(name: String): File { - return scopeCatching { - Files.move(this.toPath(), File(this.parent, name).toPath()) - }.getOrNull()?.toFile() ?: scopeCatching { - val targetFile = File(this.parent, name) - if (this.renameTo(targetFile)) { - targetFile - } else { - null - } - }.getOrNull() ?: this -} - -fun findSystemRoots(): List { - val windowsRoot = systemEnv("SystemDrive") - val roots = (scopeCatching { - FileSystems.getDefault()?.rootDirectories?.mapNotNull { - it?.toFile() - } - }.getOrNull()?.ifEmpty { null } ?: scopeCatching { - File.listRoots().filterNotNull() - }.getOrNull()?.toList()?.ifEmpty { null } ?: emptyList()).normalize() - - return (if (!windowsRoot.isNullOrBlank()) { - roots.sortedByDescending { - it.canonicalPath.trim().equals(windowsRoot, true) || it.isSame(File(windowsRoot)) - } - } else { - roots - }) -} - -fun File.isDirectorySafely(): Boolean { - return scopeCatching { - this.isDirectory - }.getOrNull() ?: scopeCatching { - Files.isDirectory(this.toPath()) - }.getOrNull() ?: false -} - -fun File.parentSafely(): File? { - return scopeCatching { - this.toPath().parent?.toFile() - }.getOrNull() ?: scopeCatching { - this.parentFile - }.getOrNull() -} - -fun Collection.existsSafely(): List { - return this.mapNotNull { - if (it.existsSafely()) { - it - } else { - null - } - } -} - -fun Collection.existsRSafely(): List { - return this.mapNotNull { - if (it.existsRSafely()) { - it - } else { - null - } - } -} - -fun Collection.existsRWSafely(): List { - return this.mapNotNull { - if (it.existsRWSafely()) { - it - } else { - null - } - } -} \ No newline at end of file diff --git a/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendSystem.kt b/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendSystem.kt deleted file mode 100644 index 96220145..00000000 --- a/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/ExtendSystem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package dev.datlag.burningseries.model.common - -import java.io.File - -fun systemProperty(key: String): String? = scopeCatching { - System.getProperty(key).ifEmpty { - null - } -}.getOrNull() - -fun systemProperty(key: String, value: String): String? = scopeCatching { - System.setProperty(key, value).ifEmpty { - null - } -}.getOrNull() - -fun systemEnv(key: String): String? = scopeCatching { - System.getenv(key).ifEmpty { - null - } -}.getOrNull() - -fun homeDirectory(): File? { - return systemProperty("user.home")?.let { - File(it) - } ?: systemEnv("HOME")?.let { - File(it) - } ?: systemEnv("\$HOME")?.let { - File(it) - } -} \ No newline at end of file diff --git a/model/src/jsMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.js.kt b/model/src/jsMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.js.kt similarity index 100% rename from model/src/jsMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.js.kt rename to model/src/jsMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.js.kt diff --git a/model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.java.kt b/model/src/jvmMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.jvm.kt similarity index 100% rename from model/src/javaMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.java.kt rename to model/src/jvmMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.jvm.kt diff --git a/model/src/nativeMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.native.kt b/model/src/nativeMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.native.kt similarity index 100% rename from model/src/nativeMain/kotlin/dev/datlag/burningseries/model/common/PlatformExtendAny.native.kt rename to model/src/nativeMain/kotlin/dev/datlag/burningseries/model/common/ExtendAny.native.kt diff --git a/network/build.gradle.kts b/network/build.gradle.kts index 6b43e710..71f907d1 100644 --- a/network/build.gradle.kts +++ b/network/build.gradle.kts @@ -1,102 +1,31 @@ plugins { alias(libs.plugins.multiplatform) alias(libs.plugins.serialization) - alias(libs.plugins.ksp) - alias(libs.plugins.android.library) - alias(libs.plugins.ktorfit) apply false - alias(libs.plugins.realm) } -val artifact = VersionCatalog.artifactName("network") - -group = artifact - kotlin { jvm() - androidTarget() + iosX64() iosArm64() iosSimulatorArm64() - js(IR) { - browser() - nodejs() - binaries.executable() - } - - jvmToolchain(CompileOptions.jvmTargetVersion) applyDefaultHierarchyTemplate() sourceSets { - val commonMain by getting { - dependencies { - api(project(":model")) - - api(libs.ktorfit) - api(libs.serialization.json) - api(libs.coroutines) - api(libs.flowredux) - api(libs.ktsoup) - api(libs.ktsoup.fs) - api(libs.ktsoup.ktor) - implementation(libs.jsunpacker) - api(libs.firebase.auth) - api(libs.firebase.store) - } + commonMain.dependencies { + api(libs.coroutines) + implementation(libs.datetime) + api(libs.napier) + implementation(libs.ksoup) + implementation(libs.ktor) + implementation(libs.tooling) + implementation(libs.immutable) + api(libs.flowredux) + api(libs.skeo) + + api(project(":model")) + implementation(project(":firebase")) } - - val realmCompatible by creating { - dependsOn(commonMain) - dependencies { - api(libs.realm) - api(libs.realm.sync) - } - } - - val androidMain by getting { - dependsOn(realmCompatible) - dependencies { - api(libs.firebase.android) - api(libs.firebase.android.auth) - api(libs.firebase.android.firestore) - } - } - - jvmMain.get().dependsOn(realmCompatible) - nativeMain.get().dependsOn(realmCompatible) } -} - -android { - compileSdk = Configuration.compileSdk - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - - namespace = artifact - - defaultConfig { - minSdk = Configuration.minSdk - } - - compileOptions { - sourceCompatibility = CompileOptions.sourceCompatibility - targetCompatibility = CompileOptions.targetCompatibility - } - packaging { - resources.merges.add("META-INF/LICENSE") - resources.merges.add("META-INF/DEPENDENCIES") - resources.pickFirsts.add("**") - resources.pickFirsts.add("**/*") - resources.pickFirsts.add("*") - resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin") - } -} - -dependencies { - add("kspCommonMainMetadata", libs.ktorfit.ksp) - add("kspAndroid", libs.ktorfit.ksp) - add("kspJvm", libs.ktorfit.ksp) - add("kspIosX64", libs.ktorfit.ksp) - add("kspIosArm64", libs.ktorfit.ksp) - add("kspIosSimulatorArm64", libs.ktorfit.ksp) - add("kspJs", libs.ktorfit.ksp) } \ No newline at end of file diff --git a/network/src/androidMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.android.kt b/network/src/androidMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.android.kt deleted file mode 100644 index 384be6ae..00000000 --- a/network/src/androidMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.android.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore -import dev.gitlive.firebase.firestore.where - -actual object FireStore { - actual suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List { - return (suspendCatching { - firestore.collection("stream").where("id", inArray = idList).get().documents.map { - it.get("url") - } - }.getOrNull() ?: emptyList()).ifEmpty { - RESTFireStore.getStreams(firestore, firestoreApi, idList) - } - } - - actual suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean { - if (firestore == null) { - return RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } - - return suspendCatching { - val existing = firestore - .collection("stream") - .where("id", equalTo = data.id) - .get() - .documents - .firstOrNull() - ?.reference - ?: firestore.collection("stream").document - - firestore.runTransaction { - set(documentRef = existing, data = data, merge = true) - } - }.isSuccess || RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/BurningSeries.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/BurningSeries.kt new file mode 100644 index 00000000..dec68a0d --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/BurningSeries.kt @@ -0,0 +1,303 @@ +package dev.datlag.burningseries.network + +import com.fleeksoft.ksoup.Ksoup +import com.fleeksoft.ksoup.nodes.Document +import dev.datlag.burningseries.model.BSUtil +import dev.datlag.burningseries.model.Home +import dev.datlag.burningseries.model.SearchItem +import dev.datlag.burningseries.model.Series +import dev.datlag.burningseries.network.common.allClass +import dev.datlag.burningseries.network.common.allTag +import dev.datlag.burningseries.network.common.firstClass +import dev.datlag.burningseries.network.common.firstTag +import dev.datlag.burningseries.network.common.href +import dev.datlag.burningseries.network.common.parseGet +import dev.datlag.burningseries.network.common.src +import dev.datlag.burningseries.network.common.title +import dev.datlag.tooling.async.suspendCatching +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +internal data object BurningSeries { + + private suspend fun document(client: HttpClient, url: String): Document? = suspendCatching { + return@suspendCatching suspendCatching { + Ksoup.parseGet( + url = BSUtil.getBurningSeriesLink(url), + client = client + ) + }.getOrNull() ?: suspendCatching { + Ksoup.parseGet( + url = BSUtil.getBurningSeriesLink(url, true), + client = client + ) + }.getOrNull() + }.getOrNull() + + private suspend fun latestEpisodes(client: HttpClient, document: Document) = coroutineScope { + document.getElementById("newest_episodes")?.getElementsByTag("li")?.map { li -> + async { + val episodeLinkElement = li.firstTag("a") + val episodeTitle = episodeLinkElement?.title() + val episodeHref = episodeLinkElement?.href()?.let(BSUtil::fixSeriesHref) + val episodeInfoElement = li.firstClass("info") + val episodeInfo = episodeInfoElement?.text() + val episodeFlagElements = episodeInfoElement?.getElementsByTag("i") + + val episodeInfoFlags: MutableList = mutableListOf() + episodeFlagElements?.forEach { infoFlags -> + val flagClass = infoFlags.className() + val flagTitle = infoFlags.title() + + if (flagClass.isNotBlank()) { + episodeInfoFlags.add( + Home.Episode.Flag( + clazz = flagClass, + title = flagTitle + ) + ) + } + } + + if (!episodeTitle.isNullOrBlank() && !episodeHref.isNullOrBlank()) { + Home.Episode( + fullTitle = episodeTitle, + href = episodeHref, + info = episodeInfo?.ifBlank { null }, + flags = episodeInfoFlags.toImmutableSet(), + coverHref = cover(client, episodeHref) + ) + } else { + null + } + } + }?.awaitAll()?.filterNotNull() ?: persistentListOf() + } + + private suspend fun latestSeries(client: HttpClient, document: Document) = coroutineScope { + document.getElementById("newest_series")?.select("li")?.map { + async { + val seriesTitle = it.selectFirst("a")?.title() + val seriesHref = it.selectFirst("a")?.href()?.let(BSUtil::fixSeriesHref) + + if (!seriesTitle.isNullOrBlank() && !seriesHref.isNullOrBlank()) { + + Home.Series( + title = seriesTitle, + href = seriesHref, + coverHref = cover(client, seriesHref) + ) + } else { + null + } + } + }?.awaitAll()?.filterNotNull() ?: persistentListOf() + } + + internal suspend fun home(client: HttpClient): Home? { + val homeDoc = document(client, "") ?: return null + val episodes = latestEpisodes(client, homeDoc) + val series = latestSeries(client, homeDoc) + + return if (series.isNotEmpty()) { + Home( + episodes = episodes.toImmutableSet(), + series = series.toImmutableSet() + ) + } else { + null + } + } + + internal suspend fun search(client: HttpClient): ImmutableSet { + val doc = document(client, BSUtil.SEARCH) ?: return persistentSetOf() + + return doc.getElementById("seriesContainer")?.allClass("genre")?.map { element -> + val genre = element.firstTag("strong")?.text()?.ifBlank { null } + + element.getElementsByTag("li").mapNotNull { li -> + val linkElement = li.firstTag("a") + val title = linkElement?.text()?.ifBlank { null } + val href = linkElement?.href()?.ifBlank { null }?.let(BSUtil::fixSeriesHref) + + if (!title.isNullOrBlank() && !href.isNullOrBlank()) { + SearchItem( + title = title, + href = href, + genre = genre + ) + } else { + null + } + } + }?.flatten()?.toImmutableSet() ?: persistentSetOf() + } + + internal suspend fun series(client: HttpClient, href: String): Series? { + val doc = document(client, BSUtil.fixSeriesHref(href)) ?: return null + + val titleElement = doc.firstClass("serie")?.firstTag("h2") ?: return null + val titleSeason = titleElement.firstTag("small")?.text()?.trim() + val title = titleElement.text().replace(titleSeason ?: "", "").trim() + val description = doc.firstClass("serie")?.selectFirst("#sp_left > p")?.text() + + val seasons = doc.firstClass("serie") + ?.getElementById("seasons") + ?.firstTag("ul") + ?.allTag("li") + ?.mapIndexed { index, element -> + val seasonTitle = element.firstTag("a")?.text()?.ifBlank { null } ?: element.text() + val link = (element.firstTag("a")?.href() ?: element.href())?.let(BSUtil::fixSeriesHref) + val value = if (link.isNullOrBlank()) { + index + } else { + link.let(BSUtil::seasonFrom) ?: index + } + + Series.Season( + value = value, + title = seasonTitle.trim() + ) + }?.toImmutableSet() ?: persistentSetOf() + + val selectedLanguageValue = doc.firstClass("series-language")?.selectFirst("option[selected]")?.value()?.ifBlank { null } + var selectedLanguage: String? = null + val languageElements = doc.firstClass("series-language")?.select("option").orEmpty() + + val languages = languageElements.mapNotNull { + val value = it.value().ifBlank { null } + val text = it.text() + val selected = it.selectFirst("option[selected]")?.value() + + if (!selected.isNullOrBlank() || (!selectedLanguageValue.isNullOrBlank() && selectedLanguageValue == value)) { + selectedLanguage = value + } + if (!value.isNullOrBlank() && text.isNotBlank()) { + Series.Language( + value = value.trim(), + title = text + ) + } else { + null + } + }.toImmutableSet() + + if (selectedLanguage.isNullOrBlank()) { + selectedLanguage = selectedLanguageValue + if (selectedLanguage.isNullOrBlank()) { + selectedLanguage = languages.firstOrNull()?.value + } + } + + val infoElement = doc.firstClass("serie")?.firstClass("infos") + val infoHeader = infoElement?.select("div > span").orEmpty().map { + it.text().trim() + } + val infoData = infoElement?.allTag("p").orEmpty().map { + it.text().trim() + }.toMutableList().apply { + remove(description) + remove(description?.trim()) + } + val infoList: MutableList = mutableListOf() + + for (i in infoHeader.indices) { + val data = if (infoData.size > i && infoData[i].isNotBlank()) { + infoData[i].replace(Regex("(?:(\\n)*\\t)+", setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)), "\t") + } else { + "" + } + + infoList.add( + Series.Info( + infoHeader[i], + data + ) + ) + } + + val episodesElement = doc.firstClass("serie")?.firstClass("episodes")?.allTag("tr").orEmpty() + val episodeInfoList = episodesElement.mapNotNull { element -> + val episodeList = element.allTag("td").flatMap { it.allTag("a") }.map { data -> + val text = data.text() + val episodeHref = data.href()?.let(BSUtil::normalizeHref) + + text.trim() to episodeHref + } + + val episodeHref = when { + episodeList.isEmpty() -> return@mapNotNull null + !episodeList[0].second.isNullOrBlank() -> episodeList[0].second + episodeList.size > 1 && !episodeList[1].second.isNullOrBlank() -> episodeList[1].second + else -> return@mapNotNull null + } ?: return@mapNotNull null + + val episodeTitle = if (episodeList.size > 1) episodeList[1].first.trim() else "" + + val hoster = episodeList.map { it.second }.filterNot { it.isNullOrBlank() }.toMutableSet().apply { + remove(episodeHref) + remove(episodeHref.trim()) + }.filterNotNull() + + val hosterList = hoster.map { + Series.Episode.Hoster( + title = it.replace(episodeHref, "").replace("/", ""), + href = it + ) + } + + Series.Episode( + number = episodeList[0].first.trim(), + fullTitle = episodeTitle, + href = episodeHref, + hoster = hosterList.toImmutableSet() + ) + } + + return Series( + title = title, + description = description ?: "", + coverHref = cover(doc) ?: cover(client, href), + href = doc.location()?.let(BSUtil::fixSeriesHref) ?: BSUtil.fixSeriesHref(href), + seasonTitle = titleSeason ?: "", + seasons = seasons, + selectedLanguage = selectedLanguage?.trim(), + languages = languages, + info = infoList.toImmutableSet(), + episodes = episodeInfoList.toImmutableSet() + ) + } + + private suspend fun cover(client: HttpClient, url: String): String? { + val coverDoc = document( + client, + BSUtil.commonSeriesHref(url) + ) ?: document( + client, + BSUtil.fixSeriesHref(url) + ) ?: document( + client, + url + ) + + return coverDoc?.let { cover(it) } + } + + private suspend fun cover(document: Document): String? { + val seriesElement = document.firstClass("serie") + val allImages = seriesElement?.getElementsByTag("img") + + val cover = (allImages?.firstOrNull { + it.attr("alt").equals("Cover", ignoreCase = true) + } ?: allImages?.firstOrNull())?.src() + + return cover?.let(BSUtil::getBurningSeriesLink) + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/EpisodeStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/EpisodeStateMachine.kt new file mode 100644 index 00000000..00886a6e --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/EpisodeStateMachine.kt @@ -0,0 +1,105 @@ +package dev.datlag.burningseries.network + +import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.network.state.EpisodeAction +import dev.datlag.burningseries.network.state.EpisodeState +import dev.datlag.skeo.Skeo +import dev.datlag.tooling.async.suspendCatching +import io.ktor.client.HttpClient +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +@OptIn(ExperimentalCoroutinesApi::class) +class EpisodeStateMachine( + private val client: HttpClient, + private val firebaseAuth: FirebaseFactory.Auth?, + private val fireStore: FirebaseFactory.Store?, + private val crashlytics: FirebaseFactory.Crashlytics? +) : FlowReduxStateMachine( + initialState = EpisodeState.None +) { + + init { + spec { + inState { + onEnterEffect { + if (firebaseAuth?.isSignedIn != true) { + firebaseAuth?.signInAnonymously() + } + } + on { action, state -> + state.override { EpisodeState.Loading(action.episode) } + } + on { action, state -> + if (state.snapshot is EpisodeState.SuccessStream) { + state.noChange() + } else { + state.override { EpisodeState.Loading(action.episode) } + } + } + on { _, state -> + state.override { EpisodeState.None } + } + } + inState { + onEnter { state -> + val hosterHref = state.snapshot.episode.hoster.map { it.href } + + val firebaseResult = suspendCatching { + fireStore?.streams(hosterHref) + } + val lists = firebaseResult.getOrNull() + + state.override { + if (lists.isNullOrEmpty()) { + EpisodeState.ErrorHoster( + throwable = firebaseResult.exceptionOrNull(), + episode = state.snapshot.episode + ) + } else { + EpisodeState.SuccessHoster( + episode = state.snapshot.episode, + results = lists.toImmutableSet() + ) + } + } + } + } + inState { + onEnter { state -> + val urls = state.snapshot.results + val streams = coroutineScope { + urls.map { + async { + Skeo.loadVideos(client, it) + } + }.awaitAll() + }.flatten() + + state.override { + if (streams.isEmpty()) { + EpisodeState.ErrorStream( + throwable = null, + episode = state.snapshot.episode + ) + } else { + EpisodeState.SuccessStream( + episode = state.snapshot.episode, + results = streams.toImmutableSet() + ) + } + } + } + } + inState { + onEnterEffect { + crashlytics?.log(it.throwable) + } + } + } + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/Firestore.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/Firestore.kt deleted file mode 100644 index 742ff720..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/Firestore.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.network - -import de.jensklingenberg.ktorfit.http.* -import dev.datlag.burningseries.model.FirestoreDocument -import dev.datlag.burningseries.model.FirestoreQuery -import io.ktor.client.statement.* - -interface Firestore { - - @Headers( - "Content-Type: application/json", - "Accept: application/json" - ) - @POST("databases/(default)/documents:runQuery") - suspend fun query(@Body request: FirestoreQuery): List - - @Headers( - "Content-Type: application/json", - "Accept: application/json" - ) - @PATCH("databases/(default)/documents/{collection}") - suspend fun patch(@Header("Authorization") token: String, @Path("collection") collection: String, @Body request: FirestoreDocument): HttpResponse - - @Headers( - "Content-Type: application/json", - "Accept: application/json" - ) - @POST("databases/(default)/documents/{collection}") - suspend fun create(@Header("Authorization") token: String, @Path("collection") collection: String, @Body request: FirestoreDocument): HttpResponse -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/HomeStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/HomeStateMachine.kt new file mode 100644 index 00000000..45a0f7db --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/HomeStateMachine.kt @@ -0,0 +1,62 @@ +package dev.datlag.burningseries.network + +import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.network.state.HomeAction +import dev.datlag.burningseries.network.state.HomeState +import dev.datlag.tooling.async.suspendCatching +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +class HomeStateMachine( + private val client: HttpClient, + private val crashlytics: FirebaseFactory.Crashlytics? +) : FlowReduxStateMachine( + initialState = currentState +) { + + var currentState: HomeState + get() = Companion.currentState + private set(value) { + Companion.currentState = value + } + + init { + spec { + inState { + onEnterEffect { + currentState = it + } + } + inState { + onEnter { state -> + val result = suspendCatching { + BurningSeries.home(client) + } + + state.override { + HomeState.fromResult(result) + } + } + } + inState { + onEnterEffect { + crashlytics?.log(it.throwable) + } + on { _, state -> + state.override { HomeState.Loading } + } + } + } + } + + companion object { + var currentState: HomeState + get() = StateSaver.homeState + private set(value) { + StateSaver.homeState = value + } + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/JsonBase.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/JsonBase.kt deleted file mode 100644 index 76007a9c..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/JsonBase.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.datlag.burningseries.network - -import de.jensklingenberg.ktorfit.http.* -import dev.datlag.burningseries.model.JsonBaseCaptchaEntry - -interface JsonBase { - - @Headers("Accept: application/json") - @GET("bs-decaptcha/{id}") - suspend fun burningSeriesCaptcha( - @Path("id") id: String - ): JsonBaseCaptchaEntry - - @Headers( - "Content-Type: application/json", - "Accept: application/json" - ) - @PUT("bs-decaptcha/{id}") - suspend fun setBurningSeriesCaptcha( - @Path("id") id: String, - @Body body: JsonBaseCaptchaEntry - ): JsonBaseCaptchaEntry -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SaveStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SaveStateMachine.kt new file mode 100644 index 00000000..a51f3817 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SaveStateMachine.kt @@ -0,0 +1,92 @@ +package dev.datlag.burningseries.network + +import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.network.state.SaveAction +import dev.datlag.burningseries.network.state.SaveState +import dev.datlag.skeo.Skeo +import dev.datlag.tooling.async.suspendCatching +import io.ktor.client.HttpClient +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +class SaveStateMachine( + private val client: HttpClient, + private val streamClient: HttpClient?, + private val firebaseAuth: FirebaseFactory.Auth?, + private val fireStore: FirebaseFactory.Store?, + private val crashlytics: FirebaseFactory.Crashlytics? +) : FlowReduxStateMachine( + initialState = SaveState.None +) { + + init { + spec { + inState { + onEnterEffect { + if (firebaseAuth?.isSignedIn != true) { + firebaseAuth?.signInAnonymously() + } + } + on { _, state -> + state.override { SaveState.None } + } + } + inState { + on { action, state -> + state.override { SaveState.Saving(action.episodeHref, action.data) } + } + } + inState { + onEnter { state -> + val firebaseSaved = suspendCatching { + fireStore?.addStream( + data = state.snapshot.data.fireStore + ) + }.getOrNull() ?: false + + val seriesResult = suspendCatching { + state.snapshot.episodeHref?.let { href -> + BurningSeries.series(client, href) + } + } + val series = seriesResult.getOrNull() + + val episode = state.snapshot.episodeHref?.let { href -> + series?.episodes?.firstOrNull { + it.href == href + } ?: series?.episodes?.firstOrNull { + it.href.equals(href, ignoreCase = true) + } + } + + val stream = suspendCatching { + Skeo.loadVideos(streamClient ?: client, state.snapshot.data.url) + }.getOrNull().orEmpty().toImmutableSet() + + state.override { + if (firebaseSaved) { + SaveState.Success(series, episode, stream) + } else { + SaveState.Error(seriesResult.exceptionOrNull(), series, episode, stream) + } + } + } + } + inState { + on { action, state -> + state.override { SaveState.Saving(action.episodeHref, action.data) } + } + } + inState { + onEnterEffect { + crashlytics?.log(it.throwable) + } + on { action, state -> + state.override { SaveState.Saving(action.episodeHref, action.data) } + } + } + } + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SearchStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SearchStateMachine.kt new file mode 100644 index 00000000..9a94428a --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SearchStateMachine.kt @@ -0,0 +1,96 @@ +package dev.datlag.burningseries.network + +import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.model.algorithm.JaroWinkler +import dev.datlag.burningseries.network.state.SearchAction +import dev.datlag.burningseries.network.state.SearchState +import dev.datlag.tooling.async.suspendCatching +import io.github.aakira.napier.Napier +import io.ktor.client.HttpClient +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope + +@OptIn(ExperimentalCoroutinesApi::class) +class SearchStateMachine( + private val client: HttpClient, + private val crashlytics: FirebaseFactory.Crashlytics? +) : FlowReduxStateMachine( + initialState = currentState +) { + + var currentState: SearchState + get() = Companion.currentState + private set(value) { + Companion.currentState = value + } + + init { + spec { + inState { + onEnterEffect { + currentState = it + } + } + inState { + onEnter { state -> + val result = suspendCatching { + BurningSeries.search(client) + } + + state.override { + SearchState.fromResult(result) + } + } + } + inState { + onEnterEffect { + crashlytics?.log(it.throwable) + } + on { _, state -> + state.override { SearchState.Loading } + } + } + inState { + on { action, state -> + val search = if (action.query.isNullOrBlank()) { + persistentSetOf() + } else { + coroutineScope { + state.snapshot.allItems.map { + async { + when { + it.title.equals(action.query, ignoreCase = true) -> it to 1.0 + it.mainTitle.equals(action.query, ignoreCase = true) -> it to 0.99 + it.subTitle.equals(action.query, ignoreCase = true) -> it to 0.96 + it.title.startsWith(action.query, ignoreCase = true) -> it to 0.95 + it.title.contains(action.query, ignoreCase = true) -> it to 0.9 + else -> it to JaroWinkler.distance(it.title, action.query) + } + } + }.awaitAll().filter { + it.second > 0.85 + }.sortedByDescending { it.second }.map { it.first }.toImmutableSet() + } + } + + state.mutate { + copy(queriedItems = search) + } + } + } + } + } + + companion object { + var currentState: SearchState + get() = StateSaver.searchState + private set(value) { + StateSaver.searchState = value + } + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SeriesStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SeriesStateMachine.kt new file mode 100644 index 00000000..2705278c --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/SeriesStateMachine.kt @@ -0,0 +1,61 @@ +package dev.datlag.burningseries.network + +import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import dev.datlag.burningseries.firebase.FirebaseFactory +import dev.datlag.burningseries.network.state.SeriesAction +import dev.datlag.burningseries.network.state.SeriesState +import dev.datlag.tooling.async.suspendCatching +import io.ktor.client.HttpClient +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.flow.update + +@OptIn(ExperimentalCoroutinesApi::class) +class SeriesStateMachine( + private val client: HttpClient, + private val crashlytics: FirebaseFactory.Crashlytics?, +) : FlowReduxStateMachine( + initialState = SeriesState.Loading +) { + + var currentState: SeriesState = SeriesState.Loading + private set + + private val seriesHref: MutableStateFlow = MutableStateFlow(null) + + init { + spec { + inState { + onEnterEffect { + currentState = it + } + collectWhileInState(seriesHref) { href, state -> + if (href.isNullOrBlank()) { + return@collectWhileInState state.override { SeriesState.Loading } + } + + val result = suspendCatching { + BurningSeries.series(client, href) + } + + state.override { SeriesState.fromResult(result) } + } + } + inState { + onEnterEffect { + crashlytics?.log(it.throwable) + } + onActionEffect { _, _ -> + val previous = seriesHref.getAndUpdate { null } + seriesHref.update { previous } + } + } + } + } + + fun href(value: String) { + seriesHref.update { value } + } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/StateSaver.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/StateSaver.kt new file mode 100644 index 00000000..fb21ed65 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/StateSaver.kt @@ -0,0 +1,9 @@ +package dev.datlag.burningseries.network + +import dev.datlag.burningseries.network.state.HomeState +import dev.datlag.burningseries.network.state.SearchState + +internal data object StateSaver { + var homeState: HomeState = HomeState.Loading + var searchState: SearchState = SearchState.Loading +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/WrapAPI.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/WrapAPI.kt deleted file mode 100644 index 1374c1d5..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/WrapAPI.kt +++ /dev/null @@ -1,17 +0,0 @@ -package dev.datlag.burningseries.network - -import de.jensklingenberg.ktorfit.http.GET -import de.jensklingenberg.ktorfit.http.Query -import dev.datlag.burningseries.model.WrapAPIResponse - -interface WrapAPI { - - @GET("DatLag/burning-series/home/latest") - suspend fun getBurningSeriesHome(@Query("wrapAPIKey") apiKey: String): WrapAPIResponse - - @GET("DatLag/burning-series/series/latest") - suspend fun getBurningSeries(@Query("wrapAPIKey") apiKey: String, @Query("href") href: String): WrapAPIResponse - - @GET("DatLag/burning-series/search/latest") - suspend fun getBurningSeriesSearch(@Query("wrapAPIKey") apiKey: String): WrapAPIResponse -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendElement.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendElement.kt deleted file mode 100644 index 77e5ff26..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendElement.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.network.common - -import dev.datlag.burningseries.model.common.scopeCatching -import ktsoup.KtSoupElement - -fun KtSoupElement.hasAttr(key: String): Boolean { - return this.attrs().keys.contains(key) -} - -fun KtSoupElement.getTitle(): String { - return this.attr("title")?.ifBlank { null } ?: this.textContent() -} - -fun KtSoupElement.getHref(): String? { - return this.attr("href")?.ifBlank { null } -} - -fun KtSoupElement.getSrc(): String? { - return this.attr("src")?.ifBlank { null } ?: run { - val sources = this.querySelectorAll("source") - sources.firstOrNull()?.getSrc() - } -} - -fun KtSoupElement.getSources(): Set { - return setOfNotNull( - this.attr("src"), - *(scopeCatching { - this.querySelectorAll("source").map { it.getSources() }.flatten().toTypedArray() - }.getOrNull() ?: emptyArray()) - ) -} - -fun KtSoupElement.getValue(): String? { - return this.attr("value")?.ifBlank { null } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendKsoup.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendKsoup.kt new file mode 100644 index 00000000..1b1579c6 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/common/ExtendKsoup.kt @@ -0,0 +1,62 @@ +package dev.datlag.burningseries.network.common + +import com.fleeksoft.ksoup.Ksoup +import com.fleeksoft.ksoup.nodes.Document +import com.fleeksoft.ksoup.nodes.Element +import com.fleeksoft.ksoup.parser.Parser +import com.fleeksoft.ksoup.select.Elements +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.client.statement.request +import io.ktor.http.isSuccess + +suspend fun Ksoup.parseGet( + url: String, + client: HttpClient, + parser: Parser = Parser.htmlParser() +): Document? { + val httpResponse = client.get(url) + if (!httpResponse.status.isSuccess()) { + return null + } + + val finalUrl = httpResponse.request.url.toString() + val response = httpResponse.bodyAsText() + return parse( + html = response, + parser = parser, + baseUri = finalUrl + ) +} + +fun Element.title(): String? { + return this.attr("title").ifBlank { null } ?: this.text().ifBlank { null } +} + +fun Element.href(): String? { + return this.attr("href").ifBlank { null } +} + +fun Element.src(): String? { + return this.attr("src").ifBlank { null } ?: run { + val sources = this.select("source") + sources.firstOrNull()?.src() + } +} + +fun Element.firstClass(noDotKey: String) : Element? { + return this.getElementsByClass(noDotKey).firstOrNull() ?: this.selectFirst(".$noDotKey") +} + +fun Element.allClass(noDotKey: String): List { + return this.getElementsByClass(noDotKey).ifEmpty { this.select(".$noDotKey") } +} + +fun Element.firstTag(key: String): Element? { + return this.getElementsByTag(key).firstOrNull() ?: this.selectFirst(key) +} + +fun Element.allTag(key: String): Elements { + return this.getElementsByTag(key).ifEmpty { this.select(key) } +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.kt deleted file mode 100644 index 77059cd5..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore - -expect object FireStore { - suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List - - suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/RESTFireStore.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/RESTFireStore.kt deleted file mode 100644 index 3d4a3d9a..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/firebase/RESTFireStore.kt +++ /dev/null @@ -1,124 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.FirestoreDocument -import dev.datlag.burningseries.model.FirestoreQuery -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore -import io.ktor.http.* - -data object RESTFireStore { - - suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List { - val result = firestoreApi.query(FirestoreQuery( - structuredQuery = FirestoreQuery.StructuredQuery( - from = listOf( - FirestoreQuery.StructuredQuery.From( - collectionId = "stream" - ) - ), - where = FirestoreQuery.StructuredQuery.Where( - fieldFilter = FirestoreQuery.StructuredQuery.Where.FieldFilter( - field = FirestoreQuery.StructuredQuery.Field( - fieldPath = "id" - ), - op = FirestoreQuery.StructuredQuery.Where.FieldFilter.OP.IN, - value = FirestoreQuery.Value( - arrayValue = FirestoreQuery.Value.ArrayValue( - values = idList.map { FirestoreQuery.Value( - stringValue = it - ) } - ) - ) - ) - ), - select = FirestoreQuery.StructuredQuery.Select( - fields = listOf( - FirestoreQuery.StructuredQuery.Field( - fieldPath = "url" - ) - ) - ) - ) - )) - return result.mapNotNull { - it.document.fields["url"]?.stringValue - } - } - - suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean { - if (firestoreApi == null) { - return false - } - val token = firebaseUser?.getIdToken(false)?.let { "Bearer $it" } ?: return false - - val existing = suspendCatching { - firestoreApi.query( - FirestoreQuery( - structuredQuery = FirestoreQuery.StructuredQuery( - from = listOf( - FirestoreQuery.StructuredQuery.From( - collectionId = "stream" - ) - ), - where = FirestoreQuery.StructuredQuery.Where( - fieldFilter = FirestoreQuery.StructuredQuery.Where.FieldFilter( - field = FirestoreQuery.StructuredQuery.Field( - fieldPath = "id" - ), - op = FirestoreQuery.StructuredQuery.Where.FieldFilter.OP.EQUAL, - value = FirestoreQuery.Value( - stringValue = data.id - ) - ) - ) - ) - ) - ) - }.getOrNull()?.firstOrNull() - - val response = if (existing != null) { - val docName = existing.document.name!!.removeSuffix("/").substringAfterLast("/") - - firestoreApi.patch( - token = token, - collection = "stream/$docName", - request = FirestoreDocument( - fields = existing.document.fields.toMutableMap().apply { - put("url", FirestoreQuery.Value( - stringValue = data.url - )) - } - ) - ) - } else { - firestoreApi.create( - token = token, - collection = "stream", - request = FirestoreDocument( - fields = mapOf( - "id" to FirestoreQuery.Value( - stringValue = data.id - ), - "url" to FirestoreQuery.Value( - stringValue = data.url - ) - ) - ) - ) - } - - return response.status.isSuccess() - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt deleted file mode 100644 index 7bb8a933..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.datlag.burningseries.network.realm - -expect class RealmLoader { - suspend fun login() - - suspend fun loadEpisodes(hosterHref: List): List - - suspend fun saveEpisode(href: String, url: String): Boolean -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/scraper/BurningSeries.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/scraper/BurningSeries.kt deleted file mode 100644 index 8b6cb2ea..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/scraper/BurningSeries.kt +++ /dev/null @@ -1,292 +0,0 @@ -package dev.datlag.burningseries.network.scraper - -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.common.getHref -import dev.datlag.burningseries.network.common.getSrc -import dev.datlag.burningseries.network.common.getTitle -import dev.datlag.burningseries.network.common.getValue -import io.ktor.client.* -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import ktsoup.KtSoupDocument -import ktsoup.KtSoupParser -import ktsoup.parseRemote -import ktsoup.setClient - -data object BurningSeries { - - private suspend fun getDocument(client: HttpClient, url: String): KtSoupDocument? = suspendCatching { - KtSoupParser.setClient(client) - - return@suspendCatching suspendCatching { - KtSoupParser.parseRemote(BSUtil.getBurningSeriesLink(url)) - }.getOrNull() ?: suspendCatching { - KtSoupParser.parseRemote(BSUtil.getBurningSeriesLink(url, true)) - }.getOrNull() - }.getOrNull() - - private suspend fun getLatestEpisodes(client: HttpClient, document: KtSoupDocument) = coroutineScope { - document.getElementById("newest_episodes")?.querySelectorAll("li")?.map { - async { - val episodeTitle = it.querySelector("li")?.querySelector("a")?.getTitle() ?: String() - val episodeHref = BSUtil.normalizeHref(it.querySelector("li")?.querySelector("a")?.getHref() ?: String()) - val episodeInfo = it.querySelector("li")?.querySelector(".info")?.textContent() ?: String() - val episodeFlagElements = it.querySelector("li")?.querySelector(".info")?.querySelectorAll("i") - - val episodeInfoFlags: MutableList = mutableListOf() - episodeFlagElements?.forEach { infoFlags -> - val flagClass = infoFlags.querySelector("i")?.className() ?: String() - val flagTitle = infoFlags.querySelector("i")?.getTitle() ?: String() - episodeInfoFlags.add( - Home.Episode.Flag( - clazz = flagClass, - title = flagTitle - ) - ) - } - - if (episodeTitle.isNotEmpty() && episodeHref.isNotEmpty()) { - val (cover, isNsfw) = getCover(client, episodeHref) - - Home.Episode( - fullTitle = episodeTitle, - href = episodeHref, - info = episodeInfo, - flags = episodeInfoFlags, - coverHref = cover, - isNsfw = isNsfw - ) - } else { - null - } - } - }?.awaitAll()?.filterNotNull() ?: emptyList() - } - - private suspend fun getLatestSeries(client: HttpClient, document: KtSoupDocument) = coroutineScope { - document.getElementById("newest_series")?.querySelectorAll("li")?.map { - async { - val seriesTitle = it.querySelector("a")?.getTitle() ?: String() - val seriesHref = BSUtil.normalizeHref(it.querySelector("a")?.getHref() ?: String()) - - if (seriesTitle.isNotEmpty() && seriesHref.isNotEmpty()) { - val (cover, isNsfw) = getCover(client, seriesHref) - - Home.Series( - title = seriesTitle, - href = seriesHref, - isNsfw = isNsfw, - coverHref = cover - ) - } else { - null - } - } - }?.awaitAll()?.filterNotNull() ?: emptyList() - } - - private suspend fun getHome(client: HttpClient, document: KtSoupDocument): Home? { - val episodes = getLatestEpisodes(client, document) - val series = getLatestSeries(client, document) - - return if (episodes.isNotEmpty() && series.isNotEmpty()) { - Home( - episodes = episodes, - series = series - ) - } else { - null - } - } - - suspend fun getHome(client: HttpClient): Home? { - val doc = getDocument(client, String()) ?: return null - return getHome(client, doc) - } - - suspend fun getSeries(client: HttpClient, href: String): Series? { - val docHref = BSUtil.fixSeriesHref(href) - val doc = getDocument(client, docHref) ?: return null - - val titleElement = doc.querySelector(".serie")?.querySelector("h2") ?: return null - val titleSeason = titleElement.querySelector("small")?.textContent()?.trim() - val title = titleElement.textContent().replace(titleSeason ?: String(), String()).trim() - val description = doc.querySelector(".serie")?.querySelector("#sp_left > p")?.textContent() ?: String() - - val seasons = doc.querySelector(".serie")?.querySelector("#seasons")?.querySelector("ul")?.querySelectorAll("li")?.mapIndexed { index, it -> - val seasonTitle = it.querySelector("a")?.textContent() ?: it.textContent() - val link = BSUtil.normalizeHref(it.querySelector("a")?.getHref() ?: it.getHref() ?: String()) - val value = if (link.isBlank()) { - index - } else { - if (link.split('/').size >= 3) { - link.split('/')[2].toIntOrNull() ?: index - } else { - index - } - } - Series.Season( - value = value, - title = seasonTitle - ) - } ?: emptyList() - - val selectedLanguageValue = doc.querySelector(".series-language")?.querySelector("option[selected]")?.getValue() - var selectedLanguage: String? = null - val languageElements = doc.querySelector(".series-language")?.querySelectorAll("option") ?: emptyList() - - val languages = languageElements.mapNotNull { - val value = it.getValue() ?: String() - val text = it.textContent() - val selected = it.querySelector("option[selected]")?.getValue() - if (!selected.isNullOrBlank() || (!selectedLanguageValue.isNullOrBlank() && selectedLanguageValue == value)) { - selectedLanguage = value - } - if (value.isNotBlank() && text.isNotBlank()) { - Series.Language( - value = value, - title = text - ) - } else { - null - } - } - - if (selectedLanguage.isNullOrBlank()) { - selectedLanguage = selectedLanguageValue - if (selectedLanguage.isNullOrBlank()) { - selectedLanguage = languages.firstOrNull()?.value - } - } - - val infoHeaders = doc.querySelector(".serie")?.querySelector(".infos")?.querySelectorAll("div > span").orEmpty().map { - it.textContent().trim() - } - val infoData = doc.querySelector(".serie")?.querySelector(".infos")?.querySelectorAll("p").orEmpty().map { - it.textContent().trim() - }.toMutableList() - val infoList: MutableList = mutableListOf() - - if (infoData.contains(description)) { - infoData.removeAt(infoData.indexOf(description)) - } else if (infoData.contains(description.trim())) { - infoData.removeAt(infoData.indexOf(description.trim())) - } - - for (i in infoHeaders.indices) { - val data = if (infoData.size > i && infoData[i].isNotEmpty()) { - infoData[i].replace(Regex("(?:(\\n)*\\t)+", setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)), "\t") - } else { - "" - } - - infoList.add(Series.Info(infoHeaders[i], data)) - } - - val episodesDoc = doc.querySelector(".serie")?.querySelector(".episodes")?.querySelectorAll("tr") ?: emptyList() - val episodeInfoList = episodesDoc.mapNotNull { episodesElement -> - val episodeList = episodesElement.querySelectorAll("td").flatMap { it.querySelectorAll("a") }.map { data -> - val text = data.querySelector("a")?.textContent() ?: String() - val episodeHref = BSUtil.normalizeHref(data.querySelector("a")?.getHref() ?: String()) - - text.trim() to episodeHref - } - - val episodeHref = when { - episodeList.isEmpty() -> return@mapNotNull null - episodeList[0].second.isNotBlank() -> episodeList[0].second - episodeList.size > 1 && episodeList[1].second.isNotBlank() -> episodeList[1].second - else -> String() - } - - val episodeTitle = if (episodeList.size > 1) episodeList[1].first.trim() else String() - - val hoster = episodeList.map { it.second }.filterNot { it.isBlank() }.toMutableSet() - if (hoster.contains(episodeHref)) { - hoster.remove(episodeHref) - } else if (hoster.contains(episodeHref.trim())) { - hoster.remove(episodeHref.trim()) - } - - val hosterList = hoster.map { - Series.Episode.Hoster( - it.replace(episodeHref, String()).replace("/", ""), - it - ) - } - - Series.Episode( - number = episodeList[0].first.trim(), - title = episodeTitle, - href = episodeHref, - hosters = hosterList - ) - } - - val (cover, isNsfw) = getCover(doc) - - return Series( - title = title, - description = description, - coverHref = cover, - href = docHref, - seasonTitle = titleSeason ?: String(), - seasons = seasons, - selectedLanguage = selectedLanguage?.trim(), - languages = languages, - infos = infoList, - episodes = episodeInfoList - ) - } - - suspend fun getSearch(client: HttpClient): List { - val doc = getDocument(client, BSUtil.SEARCH) ?: return emptyList() - return doc.getElementById("seriesContainer")?.querySelectorAll(".genre")?.mapNotNull { element -> - val genre = element.querySelector("strong")?.textContent()?.trim() ?: String() - - if (genre.isNotBlank()) { - Genre( - title = genre, - items = element.querySelectorAll("li").mapNotNull { item -> - val title = item.querySelector("a")?.textContent()?.trim() ?: String() - val href = BSUtil.normalizeHref(item.querySelector("a")?.getHref()?.trim() ?: String()) - - if (title.isNotBlank() && href.isNotBlank()) { - Genre.Item( - title = title.trim(), - href = href.trim() - ) - } else { - null - } - } - ) - } else { - null - } - } ?: emptyList() - } - - private suspend fun getCover(client: HttpClient, href: String): Pair { - return getCover(getDocument(client, BSUtil.commonSeriesHref(href))) - } - - private suspend fun getCover(document: KtSoupDocument?): Pair { - val allImages = document?.querySelector(".serie")?.querySelectorAll("img") - - val cover = (allImages?.firstOrNull { - it.attr("alt").equals("Cover", true) - } ?: allImages?.firstOrNull())?.getSrc() - - val isNsfw = allImages?.firstOrNull { - it.attr("alt").equals("AB 18", true) - } != null - - return cover to isNsfw - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeState.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeState.kt new file mode 100644 index 00000000..d6c7e878 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeState.kt @@ -0,0 +1,41 @@ +package dev.datlag.burningseries.network.state + +import dev.datlag.burningseries.model.Series +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection + +sealed interface EpisodeState { + data object None : EpisodeState + + sealed interface EpisodeHolder : EpisodeState { + val episode: Series.Episode + } + + data class Loading(override val episode: Series.Episode) : EpisodeHolder + + data class SuccessHoster( + override val episode: Series.Episode, + val results: ImmutableCollection + ): EpisodeHolder + + data class SuccessStream( + override val episode: Series.Episode, + val results: ImmutableCollection + ): EpisodeHolder + + data class ErrorHoster( + internal val throwable: Throwable?, + override val episode: Series.Episode + ) : EpisodeHolder + + data class ErrorStream( + internal val throwable: Throwable?, + override val episode: Series.Episode + ) : EpisodeHolder +} + +sealed interface EpisodeAction { + data object Clear : EpisodeAction + data class Load(val episode: Series.Episode) : EpisodeAction + data class LoadNonSuccess(val episode: Series.Episode) : EpisodeAction +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeStateMachine.kt deleted file mode 100644 index 32dd4363..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/EpisodeStateMachine.kt +++ /dev/null @@ -1,142 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.algorithm.MD5 -import dev.datlag.burningseries.model.common.setFrom -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.model.state.EpisodeAction -import dev.datlag.burningseries.model.state.EpisodeState -import dev.datlag.burningseries.network.Firestore -import dev.datlag.burningseries.network.JsonBase -import dev.datlag.burningseries.network.firebase.FireStore -import dev.datlag.burningseries.network.realm.RealmLoader -import dev.datlag.skeo.Skeo -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.auth.auth -import dev.gitlive.firebase.firestore.FirebaseFirestore -import io.ktor.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope - -@OptIn(ExperimentalCoroutinesApi::class) -class EpisodeStateMachine( - private val client: HttpClient, - private val jsonBase: JsonBase, - private val realmLoader: RealmLoader, - private val firestore: FirebaseFirestore?, - private val firestoreApi: Firestore? -) : FlowReduxStateMachine(initialState = EpisodeState.Waiting) { - - init { - spec { - inState { - onEnterEffect { - realmLoader.login() - if (NetworkStateSaver.firebaseUser == null) { - NetworkStateSaver.firebaseUser = suspendCatching { - Firebase.auth.signInAnonymously().user - }.getOrNull() - } - } - on { action, state -> - state.override { EpisodeState.Loading(action.episode) } - } - } - - inState { - onEnter { state -> - val episodeHref = state.snapshot.episode.href - val hosterHref = state.snapshot.episode.hosters.map { it.href } - - val allResults = coroutineScope { - val jsonBaseResults = async { - hosterHref.map { async { - try { - val entry = jsonBase.burningSeriesCaptcha(MD5.hexString(it)) - if (!entry.broken) { - entry.url - } else { - null - } - } catch (ignored: Throwable) { - null - } - } }.awaitAll().filterNotNull() - } - - val mongoHoster = NetworkStateSaver.mongoHosterMap[episodeHref] ?: emptyList() - val mongoDBResults = async { - mongoHoster.ifEmpty { - val newList = realmLoader.loadEpisodes(hosterHref) - NetworkStateSaver.mongoHosterMap[episodeHref] = newList - newList - } - } - - val firebaseResults = async { - suspendCatching { - if (firestore != null && firestoreApi != null) { - FireStore.getStreams(firestore, firestoreApi, hosterHref) - } else { - null - } - }.getOrNull() ?: emptyList() - } - - return@coroutineScope setFrom( - jsonBaseResults.await(), - mongoDBResults.await(), - firebaseResults.await() - ) - } - - if (allResults.isNotEmpty()) { - state.override { EpisodeState.SuccessHoster( - episode = state.snapshot.episode, - results = allResults - ) } - } else { - state.override { EpisodeState.ErrorHoster(state.snapshot.episode) } - } - } - } - - inState { - onEnter { state -> - val urls = state.snapshot.results - val streams = coroutineScope { - urls.map { async { - Skeo.loadVideos(client, it) - } }.awaitAll() - }.filterNotNull() - - if (streams.isEmpty()) { - state.override { EpisodeState.ErrorStream(state.snapshot.episode) } - } else { - state.override { EpisodeState.SuccessStream(state.snapshot.episode, streams) } - } - } - } - - inState { - on { action, state -> - state.override { EpisodeState.Loading(action.episode) } - } - } - - inState { - on { action, state -> - state.override { EpisodeState.Loading(action.episode) } - } - } - - inState { - on { action, state -> - state.override { EpisodeState.Loading(action.episode) } - } - } - } - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeState.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeState.kt new file mode 100644 index 00000000..b7217d3d --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeState.kt @@ -0,0 +1,35 @@ +package dev.datlag.burningseries.network.state + +import dev.datlag.burningseries.model.Home + +sealed interface HomeState { + + val isLoading: Boolean + get() = this !is PostLoading + + val isEpisodeError: Boolean + get() = this is Failure || (this is Success && this.home.episodes.isEmpty()) + + val isSeriesError: Boolean + get() = this is Failure || (this is Success && this.home.series.isEmpty()) + + data object Loading : HomeState + + sealed interface PostLoading : HomeState + + data class Success(val home: Home) : PostLoading + + data class Failure( + internal val throwable: Throwable? + ) : PostLoading + + companion object { + fun fromResult(result: Result): HomeState { + return result.getOrNull()?.let(::Success) ?: Failure(result.exceptionOrNull()) + } + } +} + +sealed interface HomeAction { + data object Retry : HomeAction +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeStateMachine.kt deleted file mode 100644 index de886c7d..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/HomeStateMachine.kt +++ /dev/null @@ -1,85 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.model.common.suspendCatchResult -import dev.datlag.burningseries.model.state.HomeAction -import dev.datlag.burningseries.model.state.HomeState -import dev.datlag.burningseries.network.WrapAPI -import dev.datlag.burningseries.network.scraper.BurningSeries -import io.github.aakira.napier.Napier -import io.ktor.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromJsonElement - -@OptIn(ExperimentalCoroutinesApi::class) -class HomeStateMachine( - private val client: HttpClient, - private val json: Json, - private val wrapApi: WrapAPI, - private val wrapApiKey: String? -) : FlowReduxStateMachine(initialState = currentState) { - init { - spec { - inState { - onEnterEffect { - currentState = it - } - onEnter { state -> - NetworkStateSaver.Cache.home.getAlive()?.let { - return@onEnter state.override { HomeState.Success(it.first, it.second) } - } - - val result = suspendCatchResult { - var onDeviceReachable: Boolean = true - val loadedHome = BurningSeries.getHome(client) ?: run { - onDeviceReachable = false - wrapApiKey?.let { - json.decodeFromJsonElement(wrapApi.getBurningSeriesHome(it).data) - } - }!! - - if (loadedHome.episodes.isNotEmpty() || loadedHome.series.isNotEmpty()) { - NetworkStateSaver.Cache.home.cache(loadedHome to onDeviceReachable) - HomeState.Success(loadedHome, onDeviceReachable) - } else { - HomeState.Error - } - } - result.onError { - Napier.e("HomeStateError", it) - } - state.override { - result.asSuccess { - NetworkStateSaver.Cache.home.getUnAlive()?.let { - HomeState.Success(it.first, it.second) - } ?: HomeState.Error - } - } - } - } - inState { - onEnterEffect { - currentState = it - } - } - inState { - onEnterEffect { - currentState = it - } - on { _, state -> - state.override { HomeState.Loading } - } - } - } - } - - companion object { - var currentState: HomeState - set(value) { - NetworkStateSaver.initialHomeState = value - } - get() = NetworkStateSaver.initialHomeState - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/NetworkStateSaver.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/NetworkStateSaver.kt deleted file mode 100644 index 057e7da4..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/NetworkStateSaver.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.network.state - -import dev.datlag.burningseries.model.Cacheable -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.Home -import dev.datlag.burningseries.model.Release -import dev.datlag.burningseries.model.state.HomeState -import dev.datlag.burningseries.model.state.ReleaseState -import dev.datlag.burningseries.model.state.SearchState -import dev.gitlive.firebase.auth.FirebaseUser - -data object NetworkStateSaver { - - var firebaseUser: FirebaseUser? = null - - val mongoHosterMap: MutableMap> = mutableMapOf() - - // ignore EpisodeState as it's not provided as singleton - // ignore SaveState as it should reset after clearing view - // ignore SeriesState as it should reset after clearing view - var initialHomeState: HomeState = HomeState.Loading - var initialReleaseState: ReleaseState = ReleaseState.Loading - var initialSearchState: SearchState = SearchState.Loading - - internal data object Cache { - val home: Cacheable> = Cacheable() - val release: Cacheable> = Cacheable() - val search: Cacheable> = Cacheable() - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/ReleaseStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/ReleaseStateMachine.kt deleted file mode 100644 index 96811754..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/ReleaseStateMachine.kt +++ /dev/null @@ -1,64 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.common.suspendCatchResult -import dev.datlag.burningseries.model.state.ReleaseState -import dev.datlag.burningseries.network.GitHub -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@OptIn(ExperimentalCoroutinesApi::class) -class ReleaseStateMachine( - private val gitHub: GitHub -) : FlowReduxStateMachine(initialState = currentState) { - init { - spec { - inState { - onEnterEffect { - currentState = it - } - onEnter { state -> - NetworkStateSaver.Cache.release.getAlive()?.let { - return@onEnter state.override { ReleaseState.Success(it) } - } - - val result = suspendCatchResult { - val releases = gitHub.getReleases(owner = "DatL4g", repo = "Burning-Series") - val filtered = releases.filterNot { it.draft || it.preRelease } - - if (filtered.isEmpty()) { - ReleaseState.Error - } else { - ReleaseState.Success(filtered.also { - NetworkStateSaver.Cache.release.cache(it) - }) - } - }.asSuccess { - NetworkStateSaver.Cache.release.getUnAlive()?.let { - ReleaseState.Success(it) - } ?: ReleaseState.Error - } - - state.override { result } - } - } - inState { - onEnterEffect { - currentState = it - } - } - inState { - onEnterEffect { - currentState = it - } - } - } - } - - companion object { - var currentState: ReleaseState - set(value) { - NetworkStateSaver.initialReleaseState = value - } - get() = NetworkStateSaver.initialReleaseState - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveState.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveState.kt new file mode 100644 index 00000000..91e6d108 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveState.kt @@ -0,0 +1,27 @@ +package dev.datlag.burningseries.network.state + +import dev.datlag.burningseries.model.HosterScraping +import dev.datlag.burningseries.model.Series +import dev.datlag.skeo.DirectLink +import kotlinx.collections.immutable.ImmutableCollection + +sealed interface SaveState { + data object None : SaveState + data class Saving(val episodeHref: String?, val data: HosterScraping) : SaveState + data class Success( + val series: Series?, + val episode: Series.Episode?, + val stream: ImmutableCollection + ) : SaveState + data class Error( + internal val throwable: Throwable?, + val series: Series?, + val episode: Series.Episode?, + val stream: ImmutableCollection + ) : SaveState +} + +sealed interface SaveAction { + data object Clear : SaveAction + data class Save(val episodeHref: String?, val data: HosterScraping) : SaveAction +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveStateMachine.kt deleted file mode 100644 index 6b683590..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SaveStateMachine.kt +++ /dev/null @@ -1,101 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.algorithm.MD5 -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.model.state.SaveAction -import dev.datlag.burningseries.model.state.SaveState -import dev.datlag.burningseries.network.Firestore -import dev.datlag.burningseries.network.JsonBase -import dev.datlag.burningseries.network.firebase.FireStore -import dev.datlag.burningseries.network.realm.RealmLoader -import dev.datlag.skeo.Skeo -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.auth.auth -import dev.gitlive.firebase.firestore.FirebaseFirestore -import io.ktor.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope - -@OptIn(ExperimentalCoroutinesApi::class) -class SaveStateMachine( - private val client: HttpClient, - private val jsonBase: JsonBase, - private val realmLoader: RealmLoader, - private val firestore: FirebaseFirestore?, - private val firestoreApi: Firestore? -) : FlowReduxStateMachine(initialState = SaveState.Waiting) { - - init { - spec { - inState { - onEnterEffect { - realmLoader.login() - if (NetworkStateSaver.firebaseUser == null) { - NetworkStateSaver.firebaseUser = suspendCatching { - Firebase.auth.signInAnonymously().user - }.getOrNull() - } - } - - on { action, state -> - state.override { SaveState.Saving(action.data, action.loadStream) } - } - } - - inState { - onEnter { state -> - val anySuccess = coroutineScope { - val jsonBaseSaved = async { - suspendCatching { - jsonBase.setBurningSeriesCaptcha(MD5.hexString(state.snapshot.data.href), state.snapshot.data.jsonBase) - }.getOrNull() != null - } - - val mongoSaved = async { - realmLoader.saveEpisode(state.snapshot.data.href, state.snapshot.data.url) - } - - val firebaseSaved = async { - suspendCatching { - FireStore.addStream( - firebaseUser = NetworkStateSaver.firebaseUser, - firestore = firestore, - firestoreApi = firestoreApi, - data = state.snapshot.data.firestore - ) - }.getOrNull() ?: false - } - - return@coroutineScope mongoSaved.await() || firebaseSaved.await() || jsonBaseSaved.await() - } - - val stream = if (state.snapshot.loadStream) { - Skeo.loadVideos(client, state.snapshot.data.url) - } else { - null - } - - if (anySuccess) { - state.override { SaveState.Success(stream) } - } else { - state.override { SaveState.Error(stream) } - } - } - } - - inState { - on { action, state -> - state.override { SaveState.Saving(action.data, action.loadStream) } - } - } - - inState { - on { action, state -> - state.override { SaveState.Saving(action.data, action.loadStream) } - } - } - } - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchState.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchState.kt new file mode 100644 index 00000000..e838b9b5 --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchState.kt @@ -0,0 +1,55 @@ +package dev.datlag.burningseries.network.state + +import dev.datlag.burningseries.model.SearchItem +import kotlinx.collections.immutable.ImmutableCollection +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableSet + +sealed interface SearchState { + + val isLoading: Boolean + get() = this !is PostLoading + + val isError: Boolean + get() = this is Failure + + val isSuccess: Boolean + get() = !isLoading && !isError + + val hasQueryItems: Boolean + get() = this is Success && this.queriedItems.isNotEmpty() + + data object Loading : SearchState + + sealed interface PostLoading : SearchState + + data class Success( + val allItems: ImmutableSet, + val queriedItems: ImmutableSet = persistentSetOf(), + ) : PostLoading + + data class Failure( + internal val throwable: Throwable? + ) : PostLoading + + companion object { + fun fromResult(result: Result?>): SearchState { + return result.getOrNull()?.let { col -> + if (col.isEmpty()) { + Failure(result.exceptionOrNull()) + } else { + Success(col.toImmutableSet()) + } + } ?: Failure( + result.exceptionOrNull() + ) + } + } +} + +sealed interface SearchAction { + data object Retry : SearchAction + + data class Query(val query: String?) : SearchAction +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchStateMachine.kt deleted file mode 100644 index bde3e37b..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SearchStateMachine.kt +++ /dev/null @@ -1,101 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.Genre -import dev.datlag.burningseries.model.common.suspendCatchResult -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.model.state.SearchAction -import dev.datlag.burningseries.model.state.SearchState -import dev.datlag.burningseries.network.WrapAPI -import dev.datlag.burningseries.network.scraper.BurningSeries -import io.ktor.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.jsonObject - -@OptIn(ExperimentalCoroutinesApi::class) -class SearchStateMachine( - private val client: HttpClient, - private val json: Json, - private val wrapApi: WrapAPI, - private val wrapApiKey: String?, - private val saveToDB: suspend (SearchState.Success) -> Unit, - private val loadFromDB: suspend () -> List -) : FlowReduxStateMachine(initialState = currentState) { - init { - spec { - inState { - onEnterEffect { - currentState = it - } - onEnter { state -> - NetworkStateSaver.Cache.search.getAlive()?.let { - return@onEnter state.override { SearchState.Success(it) } - } - - val result = suspendCatchResult { - val loadedClient = suspendCatching { - BurningSeries.getSearch(client) - }.getOrNull() ?: emptyList() - - val loadedWeb = loadedClient.ifEmpty { - suspendCatching { - wrapApiKey?.let { - val response = wrapApi.getBurningSeriesSearch(it) - - suspendCatching { - json.decodeFromJsonElement>(response.data) - }.getOrNull() ?: suspendCatching { - response.data.jsonObject["genre"]?.let { j -> json.decodeFromJsonElement>(j) } - }.getOrNull() - } - }.getOrNull() ?: emptyList() - } - - val loadedGenres = loadedWeb.ifEmpty { - loadFromDB() - } - if (loadedGenres.isEmpty()) { - SearchState.Error - } else { - SearchState.Success(loadedGenres.also { - NetworkStateSaver.Cache.search.cache(it) - }) - } - }.asSuccess { - NetworkStateSaver.Cache.search.getUnAlive()?.let { - SearchState.Success(it) - } ?: SearchState.Error - } - - state.override { result } - } - } - - inState { - onEnterEffect { - currentState = it - saveToDB(it) - } - } - - inState { - onEnterEffect { - currentState = it - } - on { _, state -> - state.override { SearchState.Loading } - } - } - } - } - - companion object { - var currentState: SearchState - set(value) { - NetworkStateSaver.initialSearchState = value - } - get() = NetworkStateSaver.initialSearchState - } -} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesState.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesState.kt new file mode 100644 index 00000000..bf990dea --- /dev/null +++ b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesState.kt @@ -0,0 +1,29 @@ +package dev.datlag.burningseries.network.state + +import dev.datlag.burningseries.model.Series + +sealed interface SeriesState { + + val isLoading: Boolean + get() = this !is PostLoading + + data object Loading : SeriesState + + sealed interface PostLoading : SeriesState + + data class Success(val series: Series) : PostLoading + + data class Failure( + internal val throwable: Throwable? + ) : PostLoading + + companion object { + fun fromResult(result: Result): SeriesState { + return result.getOrNull()?.let(::Success) ?: Failure(result.exceptionOrNull()) + } + } +} + +sealed interface SeriesAction { + data object Retry : SeriesAction +} \ No newline at end of file diff --git a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesStateMachine.kt b/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesStateMachine.kt deleted file mode 100644 index 52714542..00000000 --- a/network/src/commonMain/kotlin/dev/datlag/burningseries/network/state/SeriesStateMachine.kt +++ /dev/null @@ -1,67 +0,0 @@ -package dev.datlag.burningseries.network.state - -import com.freeletics.flowredux.dsl.FlowReduxStateMachine -import dev.datlag.burningseries.model.BSUtil -import dev.datlag.burningseries.model.Series -import dev.datlag.burningseries.model.common.suspendCatchResult -import dev.datlag.burningseries.model.state.SeriesAction -import dev.datlag.burningseries.model.state.SeriesState -import dev.datlag.burningseries.network.WrapAPI -import dev.datlag.burningseries.network.scraper.BurningSeries -import io.ktor.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromJsonElement - -@OptIn(ExperimentalCoroutinesApi::class) -class SeriesStateMachine( - private val client: HttpClient, - private val href: String, - private val json: Json, - private val wrapAPI: WrapAPI, - private val wrapAPIKey: String? -) : FlowReduxStateMachine(SeriesState.Loading(href)) { - - init { - spec { - inState { - onEnter { state -> - val result = suspendCatchResult { - var onDeviceReachable: Boolean = true - val result = BurningSeries.getSeries(client, state.snapshot.href) ?: run { - onDeviceReachable = false - wrapAPIKey?.let { - val wrapResult = wrapAPI.getBurningSeries(it, BSUtil.fixSeriesHref(state.snapshot.href)) - json.decodeFromJsonElement(wrapResult.data) - } - }!! - SeriesState.Success(result, onDeviceReachable) - }.asSuccess { - SeriesState.Error(state.snapshot.href) - } - state.override { result } - } - on { action, state -> - state.mutate { - this.copy(href = action.href) - } - } - } - - inState { - on { _, state -> - state.override { SeriesState.Loading(href) } - } - on { action, state -> - state.override { SeriesState.Loading(action.href) } - } - } - - inState { - on { action, state -> - state.override { SeriesState.Loading(action.href) } - } - } - } - } -} \ No newline at end of file diff --git a/network/src/iosMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.ios.kt b/network/src/iosMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.ios.kt deleted file mode 100644 index 384be6ae..00000000 --- a/network/src/iosMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.ios.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore -import dev.gitlive.firebase.firestore.where - -actual object FireStore { - actual suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List { - return (suspendCatching { - firestore.collection("stream").where("id", inArray = idList).get().documents.map { - it.get("url") - } - }.getOrNull() ?: emptyList()).ifEmpty { - RESTFireStore.getStreams(firestore, firestoreApi, idList) - } - } - - actual suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean { - if (firestore == null) { - return RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } - - return suspendCatching { - val existing = firestore - .collection("stream") - .where("id", equalTo = data.id) - .get() - .documents - .firstOrNull() - ?.reference - ?: firestore.collection("stream").document - - firestore.runTransaction { - set(documentRef = existing, data = data, merge = true) - } - }.isSuccess || RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } -} \ No newline at end of file diff --git a/network/src/jsMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.js.kt b/network/src/jsMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.js.kt deleted file mode 100644 index 7b6a82aa..00000000 --- a/network/src/jsMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.js.kt +++ /dev/null @@ -1,51 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore -import dev.gitlive.firebase.firestore.where - -actual object FireStore { - actual suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List { - return (suspendCatching { - firestore.collection("stream").where("id", inArray = idList).get().documents.map { - it.get("url") - } - }.getOrNull() ?: emptyList()).ifEmpty { - RESTFireStore.getStreams(firestore, firestoreApi, idList) - } - } - - actual suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean { - if (firestore == null) { - return RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } - - return suspendCatching { - val existing = firestore - .collection("stream") - .where("id", equalTo = data.id) - .get() - .documents - .firstOrNull() - ?.reference - ?: firestore.collection("stream").document - - firestore.runTransaction { - set(documentRef = existing, data = data, merge = true) - } - }.isSuccess || RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } - -} \ No newline at end of file diff --git a/network/src/jsMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.js.kt b/network/src/jsMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.js.kt deleted file mode 100644 index aceaafc3..00000000 --- a/network/src/jsMain/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.js.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.datlag.burningseries.network.realm - -actual object RealmLoader { - actual suspend fun login() { } - - actual suspend fun loadEpisodes(hosterHref: List): List { - return emptyList() - } - - actual suspend fun saveEpisode(href: String, url: String): Boolean { - return false - } - -} \ No newline at end of file diff --git a/network/src/jvmMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.jvm.kt b/network/src/jvmMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.jvm.kt deleted file mode 100644 index 384be6ae..00000000 --- a/network/src/jvmMain/kotlin/dev/datlag/burningseries/network/firebase/FireStore.jvm.kt +++ /dev/null @@ -1,50 +0,0 @@ -package dev.datlag.burningseries.network.firebase - -import dev.datlag.burningseries.model.HosterScraping -import dev.datlag.burningseries.model.common.suspendCatching -import dev.datlag.burningseries.network.Firestore -import dev.gitlive.firebase.auth.FirebaseUser -import dev.gitlive.firebase.firestore.FirebaseFirestore -import dev.gitlive.firebase.firestore.where - -actual object FireStore { - actual suspend fun getStreams( - firestore: FirebaseFirestore, - firestoreApi: Firestore, - idList: List - ): List { - return (suspendCatching { - firestore.collection("stream").where("id", inArray = idList).get().documents.map { - it.get("url") - } - }.getOrNull() ?: emptyList()).ifEmpty { - RESTFireStore.getStreams(firestore, firestoreApi, idList) - } - } - - actual suspend fun addStream( - firebaseUser: FirebaseUser?, - firestore: FirebaseFirestore?, - firestoreApi: Firestore?, - data: HosterScraping.Firestore - ): Boolean { - if (firestore == null) { - return RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } - - return suspendCatching { - val existing = firestore - .collection("stream") - .where("id", equalTo = data.id) - .get() - .documents - .firstOrNull() - ?.reference - ?: firestore.collection("stream").document - - firestore.runTransaction { - set(documentRef = existing, data = data, merge = true) - } - }.isSuccess || RESTFireStore.addStream(firebaseUser, firestore, firestoreApi, data) - } -} \ No newline at end of file diff --git a/network/src/realmCompatible/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt b/network/src/realmCompatible/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt deleted file mode 100644 index af6355ea..00000000 --- a/network/src/realmCompatible/kotlin/dev/datlag/burningseries/network/realm/RealmLoader.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.datlag.burningseries.network.realm - -import dev.datlag.burningseries.model.common.suspendCatching -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.ext.call -import io.realm.kotlin.types.RealmAny -import org.mongodb.kbson.BsonDocument - -actual class RealmLoader(private val app: App?) { - actual suspend fun login() { - if (mongoUser == null) { - mongoUser = suspendCatching { - app?.login(Credentials.anonymous()) - }.getOrNull() - } - } - - actual suspend fun loadEpisodes(hosterHref: List): List { - return suspendCatching { - val doc = mongoUser?.functions?.call("query", hosterHref.toTypedArray()) - doc?.getArray("result")?.values?.map { it.asDocument().getString("url").value } - }.getOrNull() ?: emptyList() - } - - actual suspend fun saveEpisode(href: String, url: String): Boolean { - return suspendCatching { - mongoUser?.functions?.call("add", href, url) - }.getOrNull() != null - } - - companion object { - private var mongoUser: User? = null - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e0b633d..a153cd08 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,34 +1,20 @@ rootProject.name = "Burning-Series" - -include(":app:shared") -include(":app:shared:sekret") -include(":app:android") -include(":app:desktop") - +include(":composeApp", ":composeApp:sekret") +include(":settings") include(":model") include(":network") +include(":firebase") include(":database") - -include( - ":extension", - ":extension:base", - ":extension:content", - ":extension:background", - ":extension:background:sekret", -) +include(":github") +include(":k2k") pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() - maven { url = uri("https://jitpack.io") } - maven { url = uri("https://plugins.gradle.org/m2/") } + maven("https://jitpack.io") maven("https://jogamp.org/deployment/maven") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } } - -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.7.0") -} \ No newline at end of file diff --git a/settings/build.gradle.kts b/settings/build.gradle.kts new file mode 100644 index 00000000..6130e5cc --- /dev/null +++ b/settings/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.serialization) +} + +kotlin { + jvm() + + iosX64() + iosArm64() + iosSimulatorArm64() + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + api(libs.coroutines) + api(libs.datastore) + api(libs.immutable) + + implementation(libs.serialization.protobuf) + implementation(libs.tooling) + implementation(libs.datetime) + implementation(libs.oidc.tokenstore) + } + } +} diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreAppSettings.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreAppSettings.kt new file mode 100644 index 00000000..4ae52a96 --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreAppSettings.kt @@ -0,0 +1,46 @@ +package dev.datlag.burningseries.settings + +import androidx.datastore.core.DataStore +import dev.datlag.burningseries.settings.model.AppSettings +import dev.datlag.burningseries.settings.model.Language +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DataStoreAppSettings( + private val dataStore: DataStore +) : Settings.PlatformAppSettings { + + override val all: Flow = dataStore.data + override val language: Flow = all.map { it.language } + override val startCounter: Flow = all.map { it.startCounter } + + // Don't just override with value as we want to keep startCounter device specific + override suspend fun updateAll(value: AppSettings) { + dataStore.updateData { + it.copy( + language = value.language + ) + } + } + + override suspend fun setLanguage(language: Language) { + dataStore.updateData { + it.copy( + language = language + ) + } + } + + override suspend fun increaseStartCounter() { + dataStore.updateData { + val current = it.startCounter + it.copy( + startCounter = if (current == Int.MAX_VALUE) { + current + } else { + current + 1 + } + ) + } + } +} \ No newline at end of file diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreUserSettings.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreUserSettings.kt new file mode 100644 index 00000000..41c7164b --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/DataStoreUserSettings.kt @@ -0,0 +1,75 @@ +package dev.datlag.burningseries.settings + +import androidx.datastore.core.DataStore +import dev.datlag.burningseries.settings.model.UserSettings +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map + +class DataStoreUserSettings( + private val dataStore: DataStore +) : Settings.PlatformUserSettings() { + + override val all: Flow = dataStore.data + override val accessTokenFlow: Flow = all.map { it.github.accessToken?.ifBlank { null } } + override val idTokenFlow: Flow = all.map { it.github.idToken?.ifBlank { null } } + override val refreshTokenFlow: Flow = all.map { it.github.refreshToken?.ifBlank { null } } + + override suspend fun updateAll(value: UserSettings) { + dataStore.updateData { value } + } + + override suspend fun getAccessToken(): String? { + return dataStore.data.map { it.github.accessToken?.ifBlank { null } }.firstOrNull() + } + + override suspend fun getIdToken(): String? { + return dataStore.data.map { it.github.idToken?.ifBlank { null } }.firstOrNull() + } + + override suspend fun getRefreshToken(): String? { + return dataStore.data.map { it.github.refreshToken?.ifBlank { null } }.firstOrNull() + } + + override suspend fun removeAccessToken() { + dataStore.updateData { + it.copy( + github = it.github.copy( + accessToken = null + ) + ) + } + } + + override suspend fun removeIdToken() { + dataStore.updateData { + it.copy( + github = it.github.copy( + idToken = null + ) + ) + } + } + + override suspend fun removeRefreshToken() { + dataStore.updateData { + it.copy( + github = it.github.copy( + refreshToken = null + ) + ) + } + } + + override suspend fun saveTokens(accessToken: String, refreshToken: String?, idToken: String?) { + dataStore.updateData { + it.copy( + github = it.github.copy( + idToken = idToken, + accessToken = accessToken, + refreshToken = refreshToken + ) + ) + } + } +} \ No newline at end of file diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/Settings.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/Settings.kt new file mode 100644 index 00000000..e64f60cf --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/Settings.kt @@ -0,0 +1,28 @@ +package dev.datlag.burningseries.settings + +import dev.datlag.burningseries.settings.model.AppSettings +import dev.datlag.burningseries.settings.model.Language +import dev.datlag.burningseries.settings.model.UserSettings +import kotlinx.coroutines.flow.Flow +import org.publicvalue.multiplatform.oidc.ExperimentalOpenIdConnect +import org.publicvalue.multiplatform.oidc.tokenstore.TokenStore + +data object Settings { + + interface PlatformAppSettings { + val all: Flow + val language: Flow + val startCounter: Flow + + suspend fun updateAll(value: AppSettings) + suspend fun setLanguage(language: Language) + suspend fun increaseStartCounter() + } + + @OptIn(ExperimentalOpenIdConnect::class) + abstract class PlatformUserSettings : TokenStore() { + abstract val all: Flow + + abstract suspend fun updateAll(value: UserSettings) + } +} \ No newline at end of file diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/AppSettings.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/AppSettings.kt new file mode 100644 index 00000000..e3a79137 --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/AppSettings.kt @@ -0,0 +1,45 @@ +package dev.datlag.burningseries.settings.model + +import androidx.datastore.core.okio.OkioSerializer +import dev.datlag.tooling.async.suspendCatching +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.ProtoNumber +import okio.BufferedSink +import okio.BufferedSource + +@Serializable +@OptIn(ExperimentalSerializationApi::class) +data class AppSettings( + @ProtoNumber(1) val language: Language?, + @ProtoNumber(2) val startCounter: Int = 1 +) { + companion object SettingsSerializer : OkioSerializer { + override val defaultValue: AppSettings = AppSettings( + language = null, + ) + + private val protobuf = ProtoBuf { + encodeDefaults = true + } + + override suspend fun readFrom(source: BufferedSource): AppSettings { + return suspendCatching { + protobuf.decodeFromByteArray(source.readByteArray()) + }.getOrNull() ?: defaultValue + } + + override suspend fun writeTo(t: AppSettings, sink: BufferedSink) { + val newSink = suspendCatching { + sink.write(protobuf.encodeToByteArray(t)) + }.getOrNull() ?: sink + + suspendCatching { + newSink.emit() + }.getOrNull() + } + } +} diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/Language.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/Language.kt new file mode 100644 index 00000000..a56db1c7 --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/Language.kt @@ -0,0 +1,96 @@ +package dev.datlag.burningseries.settings.model + +import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable(with = Language.LanguageSerializer::class) +sealed interface Language : Comparable { + val code: String + + override fun compareTo(other: Language): Int { + return when { + this.code.equals(other.code, ignoreCase = true) -> 0 + this.code.startsWith(other.code, ignoreCase = true) -> 1 + other.code.startsWith(this.code, ignoreCase = true) -> 1 + else -> 2 + } + } + + fun compareToNullable(other: Language?): Int { + if (other == null) { + return 0 + } + + return compareTo(other) + } + + @Serializable + sealed interface German : Language { + + @Serializable + data object Default : German { + override val code: String = "de" + } + + @Serializable + data object Subtitle : German { + override val code: String = "des" + } + } + + @Serializable + data object English : Language { + override val code: String = "en" + } + + @Serializable + data object JapaneseSubtitle : Language { + override val code: String = "jps" + } + + companion object LanguageSerializer : KSerializer { + val all: ImmutableSet = persistentSetOf( + German.Default, + German.Subtitle, + English, + JapaneseSubtitle + ) + + fun fromString(value: String?): Language? = when { + value.equals(German.Default.code, ignoreCase = true) -> German.Default + value.equals(German.Subtitle.code, ignoreCase = true) -> German.Subtitle + value.equals(English.code, ignoreCase = true) -> English + value.equals(JapaneseSubtitle.code, ignoreCase = true) -> JapaneseSubtitle + else -> null + } + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Language", PrimitiveKind.STRING) + + @OptIn(ExperimentalSerializationApi::class) + override fun deserialize(decoder: Decoder): Language? { + return if (decoder.decodeNotNullMark()) { + fromString(decoder.decodeString()) + } else { + decoder.decodeNull() + } + } + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: Language?) { + if (value != null) { + encoder.encodeNotNullMark() + encoder.encodeString(value.code) + } else { + encoder.encodeNull() + } + } + } +} \ No newline at end of file diff --git a/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/UserSettings.kt b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/UserSettings.kt new file mode 100644 index 00000000..933635cc --- /dev/null +++ b/settings/src/commonMain/kotlin/dev/datlag/burningseries/settings/model/UserSettings.kt @@ -0,0 +1,54 @@ +package dev.datlag.burningseries.settings.model + +import androidx.datastore.core.okio.OkioSerializer +import dev.datlag.tooling.async.suspendCatching +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.ProtoNumber +import okio.BufferedSink +import okio.BufferedSource + +@Serializable +@OptIn(ExperimentalSerializationApi::class) +data class UserSettings( + @ProtoNumber(1) val github: GitHub = GitHub( + idToken = null, + accessToken = null, + refreshToken = null + ) +) { + @Serializable + data class GitHub( + @ProtoNumber(1) val idToken: String?, + @ProtoNumber(2) val accessToken: String?, + @ProtoNumber(3) val refreshToken: String?, + ) + + companion object SettingsSerializer : OkioSerializer { + override val defaultValue: UserSettings = UserSettings() + + + private val protobuf = ProtoBuf { + encodeDefaults = true + } + + override suspend fun readFrom(source: BufferedSource): UserSettings { + return suspendCatching { + protobuf.decodeFromByteArray(source.readByteArray()) + }.getOrNull() ?: defaultValue + } + + override suspend fun writeTo(t: UserSettings, sink: BufferedSink) { + val newSink = suspendCatching { + sink.write(protobuf.encodeToByteArray(t)) + }.getOrNull() ?: sink + + suspendCatching { + newSink.emit() + }.getOrNull() + } + } +}