Compare commits

...

4 Commits
5.3.19 ... dev

Author SHA1 Message Date
tom5079
9e61af7ed9 hello world 2025-03-08 10:30:41 -08:00
tom5079
d4009e8be9 revert mainactivity 2025-03-08 09:03:24 -08:00
tom5079
697eeb61d2 base component activity 2025-03-08 08:59:05 -08:00
tom5079
f6d29ac083 add compose 2025-03-08 08:53:23 -08:00
11 changed files with 559 additions and 110 deletions

View File

@@ -69,7 +69,22 @@ dependencies {
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(libs.androidx.compose.runtime) implementation(platform(libs.compose.bom))
androidTestImplementation(platform(libs.compose.bom))
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.compose.ui.tooling.preview)
debugImplementation(libs.compose.ui.tooling)
androidTestImplementation(libs.compose.ui.test)
debugImplementation(libs.compose.ui.test.manifest)
implementation(libs.compose.icons)
implementation(libs.compose.adaptive)
implementation(libs.compose.activity)
implementation(libs.compose.lifecycle.viewmodel)
implementation(libs.compose.livedata)
implementation(libs.compose.navigation)
implementation(libs.accompanist.adaptive)
implementation(libs.core.ktx) implementation(libs.core.ktx)
implementation(libs.appcompat) implementation(libs.appcompat)

View File

@@ -5,22 +5,34 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23"/> <uses-permission
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="23"/> android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="23" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="23" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" <uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" /> tools:targetApi="s" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="32" <uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32"
tools:ignore="CoarseFineLocation" /> tools:ignore="CoarseFineLocation" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application <application
android:name=".Pupil" android:name=".Pupil"
@@ -28,20 +40,20 @@
android:fullBackupContent="true" android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config" tools:ignore="UnusedAttribute"
tools:replace="android:theme" tools:replace="android:theme">
tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="face" /> android:value="face" />
<provider <provider
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
@@ -51,15 +63,18 @@
</provider> </provider>
<service android:name=".services.DownloadService" <service
android:name=".services.DownloadService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service android:name=".services.TransferClientService" <service
android:name=".services.TransferClientService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service android:name=".services.TransferServerService" <service
android:name=".services.TransferServerService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
@@ -75,8 +90,8 @@
<activity <activity
android:name=".ui.ReaderActivity" android:name=".ui.ReaderActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:parentActivityName=".ui.MainActivity" android:exported="true"
android:exported="true"> android:parentActivityName=".ui.MainComposeActivity">
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -85,8 +100,8 @@
<data android:scheme="http" /> <data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="*.hasha.in"/> <data android:host="*.hasha.in" />
<data android:pathPrefix="/reader"/> <data android:pathPrefix="/reader" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -96,8 +111,8 @@
<data android:scheme="http" /> <data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="hitomi.la"/> <data android:host="hitomi.la" />
<data android:pathPrefix="/galleries"/> <data android:pathPrefix="/galleries" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -177,13 +192,13 @@
</activity> </activity>
<activity <activity
android:name=".ui.SettingsActivity" android:name=".ui.SettingsActivity"
android:label="@string/settings_title"> android:label="@string/settings_title" />
</activity>
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainComposeActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/NoActionBarAppTheme" android:exported="true"
android:exported="true"> android:theme="@android:style/Theme.Material.Light.NoActionBar"
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -191,7 +206,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
<activity android:name=".ui.TransferActivity" /> <activity android:name=".ui.TransferActivity" />
</application> </application>

View File

@@ -26,7 +26,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@@ -38,22 +37,22 @@ import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.*
import okhttp3.Dispatcher import okhttp3.Dispatcher
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.pupil.hitomi.evaluationContext
import xyz.quaver.pupil.hitomi.readText
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.SavedSet
import xyz.quaver.pupil.util.getProxyInfo
import xyz.quaver.pupil.util.preferences
import xyz.quaver.pupil.util.proxyInfo
import java.io.File import java.io.File
import java.net.URL
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.util.* import java.util.UUID
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
@@ -94,16 +93,19 @@ fun getSSLContext(context: Context): SSLContext {
keyStore.setCertificateEntry("isrgrootx1", certificate) keyStore.setCertificateEntry("isrgrootx1", certificate)
val defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val defaultTrustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
defaultTrustManagerFactory.init(null as KeyStore?) defaultTrustManagerFactory.init(null as KeyStore?)
defaultTrustManagerFactory.trustManagers.filterIsInstance(X509TrustManager::class.java).forEach { trustManager -> defaultTrustManagerFactory.trustManagers.filterIsInstance<X509TrustManager>()
.forEach { trustManager ->
trustManager.acceptedIssuers.forEach { acceptedIssuer -> trustManager.acceptedIssuers.forEach { acceptedIssuer ->
keyStore.setCertificateEntry(acceptedIssuer.subjectDN.name, acceptedIssuer) keyStore.setCertificateEntry(acceptedIssuer.subjectDN.name, acceptedIssuer)
} }
} }
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStore) trustManagerFactory.init(keyStore)
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
@@ -142,7 +144,10 @@ class Pupil : Application() {
.proxyInfo(proxyInfo) .proxyInfo(proxyInfo)
.addInterceptor { chain -> .addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36") .addHeader(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
)
.header("Referer", "https://hitomi.la/") .header("Referer", "https://hitomi.la/")
.build() .build()
@@ -178,7 +183,8 @@ class Pupil : Application() {
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0) histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0) favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse("")) favoriteTags =
SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "") searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
favoriteTags.filter { it.tag.contains('_') }.forEach { favoriteTags.filter { it.tag.contains('_') }.forEach {
@@ -209,35 +215,60 @@ class Pupil : Application() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply { manager.createNotificationChannel(
NotificationChannel(
"download",
getString(R.string.channel_download),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_download_description) description = getString(R.string.channel_download_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("downloader", getString(R.string.channel_downloader), NotificationManager.IMPORTANCE_LOW).apply { manager.createNotificationChannel(
NotificationChannel(
"downloader",
getString(R.string.channel_downloader),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_downloader_description) description = getString(R.string.channel_downloader_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply { manager.createNotificationChannel(
NotificationChannel(
"update",
getString(R.string.channel_update),
NotificationManager.IMPORTANCE_HIGH
).apply {
description = getString(R.string.channel_update_description) description = getString(R.string.channel_update_description)
enableLights(true) enableLights(true)
enableVibration(true) enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_LOW).apply { manager.createNotificationChannel(
NotificationChannel(
"import",
getString(R.string.channel_update),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_update_description) description = getString(R.string.channel_update_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel(NotificationChannel("transfer", getString(R.string.channel_transfer), NotificationManager.IMPORTANCE_LOW).apply { manager.createNotificationChannel(
NotificationChannel(
"transfer",
getString(R.string.channel_transfer),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_transfer_description) description = getString(R.string.channel_transfer_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
@@ -245,10 +276,12 @@ class Pupil : Application() {
}) })
} }
AppCompatDelegate.setDefaultNightMode(when (Preferences.get<Boolean>("dark_mode")) { AppCompatDelegate.setDefaultNightMode(
when (Preferences.get<Boolean>("dark_mode")) {
true -> AppCompatDelegate.MODE_NIGHT_YES true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO false -> AppCompatDelegate.MODE_NIGHT_NO
}) }
)
super.onCreate() super.onCreate()
} }

View File

@@ -18,25 +18,63 @@
package xyz.quaver.pupil.ui package xyz.quaver.pupil.ui
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle import android.os.PersistableBundle
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import xyz.quaver.pupil.R import androidx.core.view.WindowCompat
import xyz.quaver.pupil.util.LockManager import xyz.quaver.pupil.util.LockManager
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.normalizeID
open class BaseActivity : AppCompatActivity() {
open class BaseComponentActivity : ComponentActivity() {
private var locked: Boolean = true private var locked: Boolean = true
private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { private val lockLauncher =
if (it.resultCode == Activity.RESULT_OK) registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK)
locked = false
else
finish()
}
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
locked = !LockManager(this).locks.isNullOrEmpty()
}
@CallSuper
override fun onResume() {
super.onResume()
if (Preferences["security_mode"])
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
if (locked)
lockLauncher.launch(Intent(this, LockActivity::class.java))
}
}
open class BaseActivity : AppCompatActivity() {
private var locked: Boolean = true
private val lockLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK)
locked = false locked = false
else else
finish() finish()
@@ -56,12 +94,12 @@ open class BaseActivity : AppCompatActivity() {
if (Preferences["security_mode"]) if (Preferences["security_mode"])
window.setFlags( window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE) WindowManager.LayoutParams.FLAG_SECURE
)
else else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
if (locked) if (locked)
lockLauncher.launch(Intent(this, LockActivity::class.java)) lockLauncher.launch(Intent(this, LockActivity::class.java))
} }
} }

View File

@@ -0,0 +1,44 @@
package xyz.quaver.pupil.ui
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.adaptive.calculateDisplayFeatures
import xyz.quaver.pupil.ui.compose.MainApp
import xyz.quaver.pupil.ui.theme.AppTheme
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
import xyz.quaver.pupil.util.requestNotificationPermission
import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog
class MainComposeActivity : BaseComponentActivity() {
private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) {
showNotificationPermissionExplanationDialog(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestNotificationPermission(this, requestNotificationPermissionLauncher, false)
val viewModel: MainViewModel by viewModels()
setContent {
val displayFeatures = calculateDisplayFeatures(this)
val uiState by viewModel.searchState.collectAsStateWithLifecycle()
AppTheme {
MainApp(
uiState = uiState,
displayFeatures = displayFeatures
)
}
}
}
}

View File

@@ -0,0 +1,20 @@
package xyz.quaver.pupil.ui.compose
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import androidx.window.core.layout.WindowSizeClass
import androidx.window.layout.DisplayFeature
import xyz.quaver.pupil.ui.viewmodel.SearchState
@Composable
fun MainApp(
uiState: SearchState,
displayFeatures: List<DisplayFeature>,
navController: NavController = rememberNavController(),
windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
) {
Text("Hello, World!")
}

View File

@@ -0,0 +1,149 @@
package xyz.quaver.pupil.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006688)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFC2E8FF)
val md_theme_light_onPrimaryContainer = Color(0xFF001E2B)
val md_theme_light_secondary = Color(0xFF4E616D)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFD1E5F3)
val md_theme_light_onSecondaryContainer = Color(0xFF091E28)
val md_theme_light_tertiary = Color(0xFF5F5A7D)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFE5DEFF)
val md_theme_light_onTertiaryContainer = Color(0xFF1C1736)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFCFE)
val md_theme_light_onBackground = Color(0xFF191C1E)
val md_theme_light_surface = Color(0xFFFBFCFE)
val md_theme_light_onSurface = Color(0xFF191C1E)
val md_theme_light_surfaceVariant = Color(0xFFDCE3E9)
val md_theme_light_onSurfaceVariant = Color(0xFF40484D)
val md_theme_light_outline = Color(0xFF71787D)
val md_theme_light_inverseOnSurface = Color(0xFFF0F1F3)
val md_theme_light_inverseSurface = Color(0xFF2E3133)
val md_theme_light_inversePrimary = Color(0xFF75D1FF)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006688)
val md_theme_light_outlineVariant = Color(0xFFC0C7CD)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF75D1FF)
val md_theme_dark_onPrimary = Color(0xFF003548)
val md_theme_dark_primaryContainer = Color(0xFF004D67)
val md_theme_dark_onPrimaryContainer = Color(0xFFC2E8FF)
val md_theme_dark_secondary = Color(0xFFB5C9D7)
val md_theme_dark_onSecondary = Color(0xFF20333D)
val md_theme_dark_secondaryContainer = Color(0xFF364954)
val md_theme_dark_onSecondaryContainer = Color(0xFFD1E5F3)
val md_theme_dark_tertiary = Color(0xFFC9C2EA)
val md_theme_dark_onTertiary = Color(0xFF312C4C)
val md_theme_dark_tertiaryContainer = Color(0xFF474364)
val md_theme_dark_onTertiaryContainer = Color(0xFFE5DEFF)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1E)
val md_theme_dark_onBackground = Color(0xFFE1E2E5)
val md_theme_dark_surface = Color(0xFF191C1E)
val md_theme_dark_onSurface = Color(0xFFE1E2E5)
val md_theme_dark_surfaceVariant = Color(0xFF40484D)
val md_theme_dark_onSurfaceVariant = Color(0xFFC0C7CD)
val md_theme_dark_outline = Color(0xFF8A9297)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1E)
val md_theme_dark_inverseSurface = Color(0xFFE1E2E5)
val md_theme_dark_inversePrimary = Color(0xFF006688)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF75D1FF)
val md_theme_dark_outlineVariant = Color(0xFF40484D)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF4FC3F7)
val Gray50 = Color(0xFFF9FAFB)
val Gray100 = Color(0xFFF3F4F6)
val Gray200 = Color(0xFFE5E7EB)
val Gray300 = Color(0xFFD1D5DB)
val Gray400 = Color(0xFF9CA3AF)
val Gray500 = Color(0xFF6B7280)
val Gray600 = Color(0xFF4B5563)
val Gray700 = Color(0xFF374151)
val Gray800 = Color(0xFF1F2937)
val Gray900 = Color(0xFF111827)
val Red50 = Color(0xFFFEF2F2)
val Red100 = Color(0xFFFEE2E2)
val Red200 = Color(0xFFFECACA)
val Red300 = Color(0xFFFCA5A5)
val Red400 = Color(0xFFF87171)
val Red500 = Color(0xFFEF4444)
val Red600 = Color(0xFFDC2626)
val Red700 = Color(0xFFB91C1C)
val Red800 = Color(0xFF991B1B)
val Red900 = Color(0xFF7F1D1D)
val Yellow50 = Color(0xFFFFFBEB)
val Yellow100 = Color(0xFFFEF3C7)
val Yellow200 = Color(0xFFFDE68A)
val Yellow300 = Color(0xFFFCD34D)
val Yellow400 = Color(0xFFFBBF24)
val Yellow500 = Color(0xFFF59E0B)
val Yellow600 = Color(0xFFD97706)
val Yellow700 = Color(0xFFB45309)
val Yellow800 = Color(0xFF92400E)
val Yellow900 = Color(0xFF78350F)
val Green50 = Color(0xFFECFDF5)
val Green100 = Color(0xFFD1FAE5)
val Green200 = Color(0xFFA7F3D0)
val Green300 = Color(0xFF6EE7B7)
val Green400 = Color(0xFF34D399)
val Green500 = Color(0xFF10B981)
val Green600 = Color(0xFF059669)
val Green700 = Color(0xFF047857)
val Green800 = Color(0xFF065F46)
val Green900 = Color(0xFF064E3B)
val Blue50 = Color(0xFFEFF6FF)
val Blue100 = Color(0xFFDBEAFE)
val Blue200 = Color(0xFFBFDBFE)
val Blue300 = Color(0xFF93C5FD)
val Blue400 = Color(0xFF60A5FA)
val Blue500 = Color(0xFF3B82F6)
val Blue600 = Color(0xFF2563EB)
val Blue700 = Color(0xFF1D4ED8)
val Blue800 = Color(0xFF1E40AF)
val Blue900 = Color(0xFF1E3A8A)
val Indigo50 = Color(0xFFEEF2FF)
val Indigo100 = Color(0xFFE0E7FF)
val Indigo200 = Color(0xFFC7D2FE)
val Indigo300 = Color(0xFFA5B4FC)
val Indigo400 = Color(0xFF818CF8)
val Indigo500 = Color(0xFF6366F1)
val Indigo600 = Color(0xFF4F46E5)
val Indigo700 = Color(0xFF4338CA)
val Indigo800 = Color(0xFF3730A3)
val Indigo900 = Color(0xFF312E81)
val Purple50 = Color(0xFFF5F3FF)
val Purple100 = Color(0xFFEDE9FE)
val Purple200 = Color(0xFFDDD6FE)
val Purple300 = Color(0xFFC4B5FD)
val Purple400 = Color(0xFFA78BFA)
val Purple500 = Color(0xFF8B5CF6)
val Purple600 = Color(0xFF7C3AED)
val Purple700 = Color(0xFF6D28D9)
val Purple800 = Color(0xFF5B21B6)
val Purple900 = Color(0xFF4C1D95)
val Pink50 = Color(0xFFFDF2F8)
val Pink100 = Color(0xFFFCE7F3)
val Pink200 = Color(0xFFFBCFE8)
val Pink300 = Color(0xFFF9A8D4)
val Pink400 = Color(0xFFF472B6)
val Pink500 = Color(0xFFEC4899)
val Pink600 = Color(0xFFDB2777)
val Pink700 = Color(0xFFBE185D)
val Pink800 = Color(0xFF9D174D)
val Pink900 = Color(0xFF831843)

View File

@@ -0,0 +1,97 @@
package xyz.quaver.pupil.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit,
) {
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colors = when {
dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
darkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}

View File

@@ -0,0 +1,12 @@
package xyz.quaver.pupil.ui.viewmodel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(SearchState())
val searchState: StateFlow<SearchState> = _uiState
}
data class SearchState(val stub: String = "")

View File

@@ -24,24 +24,26 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.serialization.json.* import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.hitomi.GalleryBlock import xyz.quaver.pupil.hitomi.GalleryBlock
import xyz.quaver.pupil.hitomi.GalleryInfo import xyz.quaver.pupil.hitomi.GalleryInfo
import xyz.quaver.pupil.hitomi.imageUrlFromImage import xyz.quaver.pupil.hitomi.imageUrlFromImage
import java.util.* import java.util.Locale
import kotlin.collections.ArrayList
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
fun String.wordCapitalize() : String { fun String.wordCapitalize(): String {
val result = ArrayList<String>() val result = ArrayList<String>()
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
@@ -59,8 +61,9 @@ private val suffix = listOf(
"TB" //really? "TB" //really?
) )
fun byteToString(byte: Long, precision : Int = 1) : String { fun byteToString(byte: Long, precision: Int = 1): String {
var size = byte.toDouble(); var suffixIndex = 0 var size = byte.toDouble()
var suffixIndex = 0
while (size >= 1024) { while (size >= 1024) {
size /= 1024 size /= 1024
@@ -92,6 +95,7 @@ val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
"-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" } "-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" }
// TODO // TODO
) )
/** /**
* Formats download folder name with given Metadata * Formats download folder name with given Metadata
*/ */
@@ -127,10 +131,10 @@ suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
} }
fun byteCount(codePoint: Int): Int = when (codePoint) { fun byteCount(codePoint: Int): Int = when (codePoint) {
in 0 ..< 0x80 -> 1 in 0..<0x80 -> 1
in 0x80 ..< 0x800 -> 2 in 0x80..<0x800 -> 2
in 0x800 ..< 0x10000 -> 3 in 0x800..<0x10000 -> 3
in 0x10000 ..< 0x110000 -> 4 in 0x10000..<0x110000 -> 4
else -> 0 else -> 0
} }
@@ -168,7 +172,10 @@ val JsonElement.content
fun checkNotificationEnabled(context: Context) = fun checkNotificationEnabled(context: Context) =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
fun showNotificationPermissionExplanationDialog(context: Context) { fun showNotificationPermissionExplanationDialog(context: Context) {
AlertDialog.Builder(context) AlertDialog.Builder(context)
@@ -182,12 +189,16 @@ fun requestNotificationPermission(
activity: Activity, activity: Activity,
requestPermissionLauncher: ActivityResultLauncher<String>, requestPermissionLauncher: ActivityResultLauncher<String>,
showRationale: Boolean = true, showRationale: Boolean = true,
ifGranted: () -> Unit, ifGranted: () -> Unit = { },
) { ) {
when { when {
checkNotificationEnabled(activity) -> ifGranted() checkNotificationEnabled(activity) -> ifGranted()
showRationale && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.POST_NOTIFICATIONS) -> showRationale && ActivityCompat.shouldShowRequestPermissionRationale(
activity,
Manifest.permission.POST_NOTIFICATIONS
) ->
showNotificationPermissionExplanationDialog(activity) showNotificationPermissionExplanationDialog(activity)
else -> else ->
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} }

View File

@@ -46,6 +46,7 @@ runner = "1.6.2"
skyfishjyLibrary = "1.0.1" skyfishjyLibrary = "1.0.1"
dotsindicator = "5.1.0" dotsindicator = "5.1.0"
workRuntimeKtx = "2.10.0" workRuntimeKtx = "2.10.0"
material3WindowSizeClassAndroid = "1.3.1"
[libraries] [libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
@@ -101,7 +102,22 @@ dotsindicator = { module = "com.tbuonomo:dotsindicator", version.ref = "dotsindi
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
recyclerview-fastscroller = { module = "com.quiph.ui:recyclerviewfastscroller", version = "1.0.0" } recyclerview-fastscroller = { module = "com.quiph.ui:recyclerviewfastscroller", version = "1.0.0" }
pinlockview = { module = "com.github.aritraroy:pinlockview", version = "2.1.0" } pinlockview = { module = "com.github.aritraroy:pinlockview", version = "2.1.0" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version = "1.7.8" }
compose-bom = { module = "androidx.compose:compose-bom", version = "2025.02.00" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-ui = { module = "androidx.compose.ui:ui" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
compose-icons = { module = "androidx.compose.material:material-icons-extended" }
compose-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" }
compose-activity = { module = "androidx.activity:activity-compose", version = "1.10.1" }
compose-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.7" }
compose-livedata = { module = "androidx.compose.runtime:runtime-livedata" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version = "2.8.8" }
accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version = "0.37.2" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }