ColorPickerDialogを少し整理
This commit is contained in:
parent
e57568a5b4
commit
15f54bdf00
|
@ -96,7 +96,6 @@ fabric.properties
|
||||||
# Generated files
|
# Generated files
|
||||||
bin/
|
bin/
|
||||||
gen/
|
gen/
|
||||||
out/
|
|
||||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
# release/
|
# release/
|
||||||
|
|
||||||
|
@ -221,3 +220,5 @@ keystore.properties
|
||||||
|
|
||||||
app/fcm/release/output-metadata.json
|
app/fcm/release/output-metadata.json
|
||||||
/_apk/
|
/_apk/
|
||||||
|
|
||||||
|
/.depCheck/
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="1.8">
|
<bytecodeTargetLevel target="19">
|
||||||
<module name="apng" target="1.7" />
|
<module name="apng" target="1.7" />
|
||||||
<module name="SubwayTooter.anko" target="17" />
|
<module name="SubwayTooter.buildSrc" target="20" />
|
||||||
<module name="SubwayTooter.apng_android" target="17" />
|
<module name="SubwayTooter.buildSrc.main" target="20" />
|
||||||
<module name="SubwayTooter.app" target="17" />
|
<module name="SubwayTooter.buildSrc.test" target="20" />
|
||||||
<module name="SubwayTooter.base" target="17" />
|
|
||||||
<module name="SubwayTooter.buildSrc" target="17" />
|
|
||||||
<module name="SubwayTooter.buildSrc.main" target="17" />
|
|
||||||
<module name="SubwayTooter.buildSrc.test" target="17" />
|
|
||||||
<module name="SubwayTooter.colorpicker" target="17" />
|
|
||||||
<module name="SubwayTooter.emoji" target="17" />
|
|
||||||
<module name="SubwayTooter.icon_material_symbols" target="17" />
|
|
||||||
<module name="SubwayTooter.main" target="17" />
|
<module name="SubwayTooter.main" target="17" />
|
||||||
<module name="SubwayTooter.sample_apng" target="17" />
|
|
||||||
<module name="SubwayTooter.test" target="17" />
|
<module name="SubwayTooter.test" target="17" />
|
||||||
</bytecodeTargetLevel>
|
</bytecodeTargetLevel>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</compositeBuild>
|
</compositeBuild>
|
||||||
</compositeConfiguration>
|
</compositeConfiguration>
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
<option name="gradleJvm" value="#JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.23" />
|
<option name="version" value="1.9.22" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -45,12 +45,14 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation("junit:junit:${Vers.junitVersion}")
|
implementation("androidx.appcompat:appcompat:${Vers.androidxAppcompat}")
|
||||||
androidTestImplementation("androidx.test.ext:junit:${Vers.androidxTestExtJunitVersion}")
|
implementation("androidx.core:core-ktx:${Vers.androidxCore}")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:${Vers.androidxTestEspressoCoreVersion}")
|
implementation("androidx.preference:preference-ktx:${Vers.androidxPreferenceKtx}")
|
||||||
|
implementation("com.google.android.material:material:${Vers.googleMaterial}")
|
||||||
|
|
||||||
implementation("androidx.appcompat:appcompat:${Vers.appcompatVersion}")
|
testImplementation(kotlin("test"))
|
||||||
implementation("androidx.core:core-ktx:${Vers.coreKtxVersion}")
|
|
||||||
implementation("androidx.preference:preference-ktx:${Vers.preferenceKtxVersion}")
|
androidTestRuntimeOnly("androidx.test:runner:1.5.2")
|
||||||
implementation("com.google.android.material:material:${Vers.materialVersion}")
|
androidTestImplementation("androidx.test:core:${Vers.androidxTestCore}")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:${Vers.androidxTestExtJunit}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package jp.juggler.anko
|
package jp.juggler.anko
|
||||||
|
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
|
|
@ -18,11 +18,11 @@ sub cmd($){
|
||||||
cmd "./gradlew --stop";
|
cmd "./gradlew --stop";
|
||||||
cmd "rm -rf .gradle/caches/build-cache-*";
|
cmd "rm -rf .gradle/caches/build-cache-*";
|
||||||
cmd "./gradlew clean";
|
cmd "./gradlew clean";
|
||||||
|
cmd "./dependencyJson.pl";
|
||||||
cmd "./gradlew assembleNoFcmRelease";
|
cmd "./gradlew assembleNoFcmRelease";
|
||||||
cmd "./gradlew assembleFcmRelease";
|
cmd "./gradlew assembleFcmRelease";
|
||||||
cmd "./gradlew --stop";
|
cmd "./gradlew --stop";
|
||||||
|
|
||||||
|
|
||||||
sub getBranch{
|
sub getBranch{
|
||||||
my $text = `git rev-parse --abbrev-ref HEAD`;
|
my $text = `git rev-parse --abbrev-ref HEAD`;
|
||||||
$text =~ s/\A\s+//;
|
$text =~ s/\A\s+//;
|
||||||
|
|
|
@ -27,7 +27,6 @@ compileTestKotlin.kotlinOptions{
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to arrayOf("*.jar"))))
|
// implementation(fileTree(mapOf("dir" to "libs", "include" to arrayOf("*.jar"))))
|
||||||
testImplementation("junit:junit:${Vers.junitVersion}")
|
testImplementation(kotlin("test"))
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${Vers.kotlinVersion}")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
package jp.juggler.apng
|
package jp.juggler.apng
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class TestApng {
|
class TestApng {
|
||||||
|
@ -3090,9 +3090,8 @@ class TestApng {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getResourceFile(path: String): File {
|
private fun getResourceFile(path: String): File =
|
||||||
return File(this.javaClass.classLoader.getResource(path).path)
|
File(javaClass.classLoader.getResource(path)!!.path)
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testByteMod() {
|
fun testByteMod() {
|
||||||
|
@ -3142,10 +3141,10 @@ class TestApng {
|
||||||
override fun onAnimationFrame(
|
override fun onAnimationFrame(
|
||||||
apng: Apng,
|
apng: Apng,
|
||||||
frameControl: ApngFrameControl,
|
frameControl: ApngFrameControl,
|
||||||
bitmap: ApngBitmap,
|
frameBitmap: ApngBitmap,
|
||||||
) {
|
) {
|
||||||
println("onAnimationFrame frameControl=$frameControl")
|
println("onAnimationFrame frameControl=$frameControl")
|
||||||
println("onAnimationFrame w=${bitmap.width},h=${bitmap.height}")
|
println("onAnimationFrame w=${frameBitmap.width},h=${frameBitmap.height}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -3154,7 +3153,6 @@ class TestApng {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test16bitPaeth() {
|
fun test16bitPaeth() {
|
||||||
|
|
||||||
FileInputStream(getResourceFile("basi2c16.png")).use { inStream ->
|
FileInputStream(getResourceFile("basi2c16.png")).use { inStream ->
|
||||||
ApngDecoder.parseStream(
|
ApngDecoder.parseStream(
|
||||||
BufferedInputStream(inStream),
|
BufferedInputStream(inStream),
|
||||||
|
@ -3200,19 +3198,19 @@ class TestApng {
|
||||||
val dst_g = dstPos.green
|
val dst_g = dstPos.green
|
||||||
val dst_b = dstPos.blue
|
val dst_b = dstPos.blue
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"xy=$x,$y color=${"0x%x".format(dstPos.color)} r",
|
|
||||||
src_r shr 8,
|
src_r shr 8,
|
||||||
dst_r
|
dst_r,
|
||||||
|
"xy=$x,$y color=${"0x%x".format(dstPos.color)} r",
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"xy=$x,$y color=${"0x%x".format(dstPos.color)} g",
|
|
||||||
src_g shr 8,
|
src_g shr 8,
|
||||||
dst_g
|
dst_g,
|
||||||
|
"xy=$x,$y color=${"0x%x".format(dstPos.color)} g",
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"xy=$x,$y color=${"0x%x".format(dstPos.color)} b",
|
|
||||||
src_b shr 8,
|
src_b shr 8,
|
||||||
dst_b
|
dst_b,
|
||||||
|
"xy=$x,$y color=${"0x%x".format(dstPos.color)} b",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3221,10 +3219,10 @@ class TestApng {
|
||||||
override fun onAnimationFrame(
|
override fun onAnimationFrame(
|
||||||
apng: Apng,
|
apng: Apng,
|
||||||
frameControl: ApngFrameControl,
|
frameControl: ApngFrameControl,
|
||||||
bitmap: ApngBitmap,
|
frameBitmap: ApngBitmap,
|
||||||
) {
|
) {
|
||||||
println("onAnimationFrame frameControl=$frameControl")
|
println("onAnimationFrame frameControl=$frameControl")
|
||||||
println("onAnimationFrame w=${bitmap.width},h=${bitmap.height}")
|
println("onAnimationFrame w=${frameBitmap.width},h=${frameBitmap.height}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -62,10 +62,13 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
||||||
|
|
||||||
api(project(":apng"))
|
api(project(":apng"))
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
implementation("com.github.bumptech.glide:glide:${Vers.glideVersion}")
|
||||||
|
implementation("com.github.zjupure:webpdecoder:${Vers.webpDecoderVersion}")
|
||||||
|
|
||||||
testImplementation("junit:junit:${Vers.junitVersion}")
|
// テストコードはない…
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ android {
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.10"
|
||||||
}
|
}
|
||||||
|
|
||||||
// exoPlayer 2.9.0 以降は Java 8 compiler support を要求する
|
// exoPlayer 2.9.0 以降は Java 8 compiler support を要求する
|
||||||
|
@ -151,9 +155,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する
|
// desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する
|
||||||
//noinspection GradleDependency
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
||||||
|
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
|
@ -161,56 +163,96 @@ dependencies {
|
||||||
implementation(project(":emoji"))
|
implementation(project(":emoji"))
|
||||||
implementation(project(":apng_android"))
|
implementation(project(":apng_android"))
|
||||||
implementation(project(":anko"))
|
implementation(project(":anko"))
|
||||||
implementation(fileTree(mapOf("dir" to "src/main/libs", "include" to arrayOf("*.aar"))))
|
|
||||||
|
|
||||||
|
implementation("androidx.activity:activity-compose:${Vers.androidxActivity}")
|
||||||
|
implementation("androidx.appcompat:appcompat:${Vers.androidxAppcompat}")
|
||||||
|
implementation("androidx.browser:browser:1.8.0")
|
||||||
|
implementation("androidx.compose.material3:material3:1.2.1")
|
||||||
|
implementation("androidx.compose.runtime:runtime-livedata:${Vers.androidxComposeRuntime}")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview:${Vers.androidxComposeUi}")
|
||||||
|
implementation("androidx.compose.ui:ui:${Vers.androidxComposeUi}")
|
||||||
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
|
implementation("androidx.drawerlayout:drawerlayout:1.2.0")
|
||||||
|
implementation("androidx.emoji2:emoji2-bundled:${Vers.androidxEmoji2}")
|
||||||
|
implementation("androidx.emoji2:emoji2-views-helper:${Vers.androidxEmoji2}")
|
||||||
|
implementation("androidx.emoji2:emoji2-views:${Vers.androidxEmoji2}")
|
||||||
|
implementation("androidx.emoji2:emoji2:${Vers.androidxEmoji2}")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:${Vers.androidxLifecycle}")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:${Vers.androidxLifecycle}")
|
||||||
|
implementation("androidx.media3:media3-common:${Vers.androidxMedia3}")
|
||||||
|
implementation("androidx.media3:media3-datasource:${Vers.androidxMedia3}")
|
||||||
|
implementation("androidx.media3:media3-exoplayer:${Vers.androidxMedia3}")
|
||||||
|
implementation("androidx.media3:media3-session:${Vers.androidxMedia3}")
|
||||||
|
implementation("androidx.media3:media3-ui:${Vers.androidxMedia3}")
|
||||||
|
implementation("androidx.recyclerview:recyclerview:${Vers.androidxRecyclerView}")
|
||||||
|
implementation("androidx.work:work-runtime-ktx:${Vers.androidxWork}")
|
||||||
|
implementation("androidx.work:work-runtime:${Vers.androidxWork}")
|
||||||
|
implementation("com.caverock:androidsvg-aar:1.4")
|
||||||
|
implementation("com.github.UnifiedPush:android-connector:2.1.1")
|
||||||
|
implementation("com.github.alexzhirkevich:custom-qr-generator:1.6.2")
|
||||||
|
implementation("com.github.bumptech.glide:glide:${Vers.glideVersion}")
|
||||||
|
implementation("com.github.omadahealth:swipy:1.2.3@aar")
|
||||||
|
implementation("com.github.penfeizhou.android.animation:apng:${Vers.apng4AndroidVersion}")
|
||||||
|
implementation("com.github.woxthebox:draglistview:1.7.3")
|
||||||
|
implementation("com.github.zjupure:webpdecoder:${Vers.webpDecoderVersion}")
|
||||||
|
implementation("com.google.android.flexbox:flexbox:${Vers.googleFlexbox}")
|
||||||
|
implementation("com.google.android.material:material:${Vers.googleMaterial}")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp-urlconnection:${Vers.okhttpVersion}")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}")
|
||||||
|
implementation("io.insert-koin:koin-android-compat:${Vers.koinVersion}")
|
||||||
|
implementation("io.insert-koin:koin-android:${Vers.koinVersion}")
|
||||||
|
implementation("io.insert-koin:koin-androidx-workmanager:${Vers.koinVersion}")
|
||||||
|
implementation("org.conscrypt:conscrypt-android:${Vers.conscryptVersion}")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Vers.kotlinxSerializationLibVersion}")
|
||||||
|
implementation("ru.gildor.coroutines:kotlin-coroutines-okhttp:${Vers.gildorkotlinCoroutinesOkhttp}")
|
||||||
|
|
||||||
|
////////////
|
||||||
|
|
||||||
|
implementation("com.github.bumptech.glide:okhttp3-integration:${Vers.glideVersion}") {
|
||||||
|
exclude("com.squareup.okhttp3", "okhttp")
|
||||||
|
}
|
||||||
|
|
||||||
"fcmImplementation"("com.google.firebase:firebase-messaging:23.4.1")
|
"fcmImplementation"("com.google.firebase:firebase-messaging:23.4.1")
|
||||||
"fcmImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:${Vers.kotlinxCoroutinesVersion}")
|
"fcmImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:${Vers.kotlinxCoroutinesVersion}")
|
||||||
|
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${Vers.detektVersion}")
|
||||||
|
|
||||||
// implementation "org.conscrypt:conscrypt-android:$conscryptVersion"
|
|
||||||
api("org.conscrypt:conscrypt-android:${Vers.conscryptVersion}")
|
|
||||||
implementation("com.github.UnifiedPush:android-connector:2.1.1")
|
|
||||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
|
||||||
|
|
||||||
// implementation("com.github.androidmads:QRGenerator:1.0.1")
|
|
||||||
implementation("com.github.alexzhirkevich:custom-qr-generator:1.6.2")
|
|
||||||
|
|
||||||
val apng4AndroidVersion = "2.25.0"
|
|
||||||
implementation("com.github.penfeizhou.android.animation:apng:$apng4AndroidVersion")
|
|
||||||
|
|
||||||
ksp("com.github.bumptech.glide:ksp:${Vers.glideVersion}")
|
ksp("com.github.bumptech.glide:ksp:${Vers.glideVersion}")
|
||||||
|
|
||||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${Vers.detektVersion}")
|
debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.3")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-tooling:1.6.3")
|
||||||
|
|
||||||
testImplementation(project(":base"))
|
// =================================================
|
||||||
androidTestImplementation(project(":base"))
|
// UnitTest
|
||||||
|
testImplementation(kotlin("test"))
|
||||||
|
testImplementation("androidx.arch.core:core-testing:${Vers.androidxArchCoreTesting}")
|
||||||
|
testImplementation("junit:junit:${Vers.junitVersion}")
|
||||||
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
||||||
|
|
||||||
androidTestApi("androidx.test.espresso:espresso-core:${Vers.androidxTestEspressoCoreVersion}")
|
// testImplementation("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
||||||
androidTestApi("androidx.test.ext:junit-ktx:1.1.5")
|
// exclude("com.squareup.okio", "okio")
|
||||||
androidTestApi("androidx.test.ext:junit:${Vers.androidxTestExtJunitVersion}")
|
// exclude("com.squareup.okhttp3", "okhttp")
|
||||||
androidTestApi("androidx.test.ext:truth:1.5.0")
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||||
androidTestApi("androidx.test:core-ktx:${Vers.testKtxVersion}")
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
androidTestApi("androidx.test:core:${Vers.androidxTestVersion}")
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||||
androidTestApi("androidx.test:runner:1.5.2")
|
// }
|
||||||
androidTestApi("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
|
||||||
androidTestApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
testApi("androidx.arch.core:core-testing:${Vers.archVersion}")
|
|
||||||
testApi("junit:junit:${Vers.junitVersion}")
|
|
||||||
testApi("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
|
||||||
testApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
|
|
||||||
// To use android test orchestrator
|
// ==============================================
|
||||||
|
// androidTest
|
||||||
|
androidTestRuntimeOnly("androidx.test:runner:1.5.2")
|
||||||
androidTestUtil("androidx.test:orchestrator:1.4.2")
|
androidTestUtil("androidx.test:orchestrator:1.4.2")
|
||||||
|
|
||||||
testApi("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
androidTestImplementation("androidx.test.espresso:espresso-core:${Vers.androidxTestEspressoCore}")
|
||||||
exclude("com.squareup.okio", "okio")
|
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||||
exclude("com.squareup.okhttp3", "okhttp")
|
androidTestImplementation("androidx.test.ext:junit:${Vers.androidxTestExtJunit}")
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
androidTestImplementation("androidx.test.ext:truth:1.5.0")
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
androidTestImplementation("androidx.test:core-ktx:${Vers.androidxTestCoreKtx}")
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
androidTestImplementation("androidx.test:core:${Vers.androidxTestCore}")
|
||||||
}
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.3")
|
||||||
androidTestApi("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
|
||||||
|
androidTestImplementation("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
||||||
exclude("com.squareup.okio", "okio")
|
exclude("com.squareup.okio", "okio")
|
||||||
exclude("com.squareup.okhttp3", "okhttp")
|
exclude("com.squareup.okhttp3", "okhttp")
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||||
|
|
|
@ -2,7 +2,7 @@ package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.jrummyapps.android.colorpicker.parseColorString
|
import com.jrummyapps.android.colorpicker.parseColor
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -12,8 +12,8 @@ class TestColorString {
|
||||||
@Test
|
@Test
|
||||||
fun testColorString() {
|
fun testColorString() {
|
||||||
fun a(s: String, expect: Int) {
|
fun a(s: String, expect: Int) {
|
||||||
assertEquals(s, expect, parseColorString(s))
|
assertEquals(s, expect, s.parseColor())
|
||||||
assertEquals("#$s", expect, parseColorString("#$s"))
|
assertEquals("#$s", expect, "#$s".parseColor())
|
||||||
}
|
}
|
||||||
a("", Color.BLACK)
|
a("", Color.BLACK)
|
||||||
a("8", Color.BLACK or 0x88_88_88)
|
a("8", Color.BLACK or 0x88_88_88)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -252,7 +252,7 @@
|
||||||
android:label="@string/app_about" />
|
android:label="@string/app_about" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ActOSSLicense"
|
android:name=".ui.ossLicense.ActOSSLicense"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/oss_license" />
|
android:label="@string/oss_license" />
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
@ -22,8 +21,9 @@ import android.widget.Spinner
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
import com.jrummyapps.android.colorpicker.ColorPickerDialogType
|
||||||
|
import com.jrummyapps.android.colorpicker.dialogColorPicker
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -77,6 +77,8 @@ import jp.juggler.util.ui.isEnabledAlpha
|
||||||
import jp.juggler.util.ui.isOk
|
import jp.juggler.util.ui.isOk
|
||||||
import jp.juggler.util.ui.scan
|
import jp.juggler.util.ui.scan
|
||||||
import jp.juggler.util.ui.vg
|
import jp.juggler.util.ui.vg
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
|
@ -94,7 +96,6 @@ import kotlin.math.max
|
||||||
|
|
||||||
class ActAccountSetting : AppCompatActivity(),
|
class ActAccountSetting : AppCompatActivity(),
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
ColorPickerDialogListener,
|
|
||||||
CompoundButton.OnCheckedChangeListener,
|
CompoundButton.OnCheckedChangeListener,
|
||||||
AdapterView.OnItemSelectedListener {
|
AdapterView.OnItemSelectedListener {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -722,13 +723,19 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
// )
|
// )
|
||||||
|
|
||||||
R.id.btnNotificationAccentColorEdit -> {
|
R.id.btnNotificationAccentColorEdit -> {
|
||||||
ColorPickerDialog.newBuilder().apply {
|
lifecycleScope.launch {
|
||||||
setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
try {
|
||||||
setAllowPresets(true)
|
account.notificationAccentColor = dialogColorPicker(
|
||||||
setShowAlphaSlider(false)
|
colorInitial = account.notificationAccentColor.notZero(),
|
||||||
setDialogId(COLOR_DIALOG_NOTIFICATION_ACCENT_COLOR)
|
alphaEnabled = false,
|
||||||
account.notificationAccentColor.notZero()?.let { setColor(it) }
|
)
|
||||||
}.show(this)
|
showNotificationColor()
|
||||||
|
saveUIToData()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
if (ex is CancellationException) return@launch
|
||||||
|
log.e(ex, "openColorPicker failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnNotificationAccentColorReset -> {
|
R.id.btnNotificationAccentColorReset -> {
|
||||||
|
@ -1518,21 +1525,6 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, newColor: Int) {
|
|
||||||
when (dialogId) {
|
|
||||||
COLOR_DIALOG_NOTIFICATION_ACCENT_COLOR -> {
|
|
||||||
account.notificationAccentColor = newColor or Color.BLACK
|
|
||||||
showNotificationColor()
|
|
||||||
saveUIToData()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showNotificationColor() {
|
private fun showNotificationColor() {
|
||||||
views.vNotificationAccentColorColor.backgroundColor =
|
views.vNotificationAccentColorColor.backgroundColor =
|
||||||
account.notificationAccentColor.notZero()
|
account.notificationAccentColor.notZero()
|
||||||
|
|
|
@ -27,17 +27,17 @@ import android.widget.FrameLayout
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.TextView.OnEditorActionListener
|
import android.widget.TextView.OnEditorActionListener
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
import com.jrummyapps.android.colorpicker.ColorPickerDialogType
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
import com.jrummyapps.android.colorpicker.dialogColorPicker
|
||||||
import jp.juggler.subwaytooter.appsetting.AppDataExporter
|
import jp.juggler.subwaytooter.appsetting.AppDataExporter
|
||||||
import jp.juggler.subwaytooter.appsetting.AppSettingItem
|
import jp.juggler.subwaytooter.appsetting.AppSettingItem
|
||||||
import jp.juggler.subwaytooter.appsetting.SettingType
|
import jp.juggler.subwaytooter.appsetting.SettingType
|
||||||
|
@ -63,8 +63,8 @@ import jp.juggler.util.backPressed
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchProgress
|
import jp.juggler.util.coroutine.launchProgress
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.data.defaultLocale
|
|
||||||
import jp.juggler.util.data.checkMimeTypeAndGrant
|
import jp.juggler.util.data.checkMimeTypeAndGrant
|
||||||
|
import jp.juggler.util.data.defaultLocale
|
||||||
import jp.juggler.util.data.intentOpenDocument
|
import jp.juggler.util.data.intentOpenDocument
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.data.notZero
|
import jp.juggler.util.data.notZero
|
||||||
|
@ -81,6 +81,8 @@ import jp.juggler.util.ui.isEnabledAlpha
|
||||||
import jp.juggler.util.ui.isNotOk
|
import jp.juggler.util.ui.isNotOk
|
||||||
import jp.juggler.util.ui.launch
|
import jp.juggler.util.ui.launch
|
||||||
import jp.juggler.util.ui.vg
|
import jp.juggler.util.ui.vg
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
@ -94,7 +96,7 @@ import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnClickListener {
|
class ActAppSetting : AppCompatActivity(), View.OnClickListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -393,22 +395,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
|
|
||||||
private fun dip(dp: Int): Int = dip(dp.toFloat())
|
private fun dip(dp: Int): Int = dip(dp.toFloat())
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
|
||||||
val colorTarget = this.colorTarget ?: return
|
|
||||||
val ip: IntPref = colorTarget.pref.cast() ?: error("$colorTarget has no in pref")
|
|
||||||
val c = when (colorTarget.type) {
|
|
||||||
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
|
||||||
else -> newColor or Color.BLACK
|
|
||||||
}
|
|
||||||
ip.value = c
|
|
||||||
val vh = findItemViewHolder(colorTarget)
|
|
||||||
vh?.showColor()
|
|
||||||
colorTarget.changed(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val settingHolderList =
|
private val settingHolderList =
|
||||||
ConcurrentHashMap<Int, VhSettingItem>()
|
ConcurrentHashMap<Int, VhSettingItem>()
|
||||||
|
|
||||||
|
@ -655,15 +641,25 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
views.btnEdit.isEnabledAlpha = item.enabled
|
views.btnEdit.isEnabledAlpha = item.enabled
|
||||||
views.btnReset.isEnabledAlpha = item.enabled
|
views.btnReset.isEnabledAlpha = item.enabled
|
||||||
views.btnEdit.setOnClickListener {
|
views.btnEdit.setOnClickListener {
|
||||||
|
actAppSetting.lifecycleScope.launch {
|
||||||
|
try {
|
||||||
actAppSetting.colorTarget = item
|
actAppSetting.colorTarget = item
|
||||||
val color = ip.value
|
val newColor = actAppSetting.dialogColorPicker(
|
||||||
val builder = ColorPickerDialog.newBuilder()
|
colorInitial = ip.value.notZero(),
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
alphaEnabled = item.type == SettingType.ColorAlpha,
|
||||||
.setAllowPresets(true)
|
)
|
||||||
.setShowAlphaSlider(item.type == SettingType.ColorAlpha)
|
val c = when (item.type) {
|
||||||
.setDialogId(COLOR_DIALOG_ID)
|
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
||||||
if (color != 0) builder.setColor(color)
|
else -> newColor or Color.BLACK
|
||||||
builder.show(actAppSetting)
|
}
|
||||||
|
ip.value = c
|
||||||
|
item.changed(actAppSetting)
|
||||||
|
actAppSetting.findItemViewHolder(item)?.showColor()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
if (ex is CancellationException) return@launch
|
||||||
|
log.e(ex, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
views.btnReset.setOnClickListener {
|
views.btnReset.setOnClickListener {
|
||||||
ip.removeValue()
|
ip.removeValue()
|
||||||
|
|
|
@ -12,31 +12,46 @@ import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
import com.jrummyapps.android.colorpicker.dialogColorPicker
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.column.*
|
import jp.juggler.subwaytooter.column.Column
|
||||||
|
import jp.juggler.subwaytooter.column.getAcctColor
|
||||||
|
import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
||||||
|
import jp.juggler.subwaytooter.column.getColumnName
|
||||||
|
import jp.juggler.subwaytooter.column.getContentColor
|
||||||
|
import jp.juggler.subwaytooter.column.getHeaderBackgroundColor
|
||||||
|
import jp.juggler.subwaytooter.column.getHeaderNameColor
|
||||||
|
import jp.juggler.subwaytooter.column.getIconId
|
||||||
|
import jp.juggler.subwaytooter.column.setHeaderBackground
|
||||||
import jp.juggler.subwaytooter.databinding.ActColumnCustomizeBinding
|
import jp.juggler.subwaytooter.databinding.ActColumnCustomizeBinding
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.checkMimeTypeAndGrant
|
||||||
|
import jp.juggler.util.data.defaultLocale
|
||||||
|
import jp.juggler.util.data.intentGetContent
|
||||||
|
import jp.juggler.util.data.mayUri
|
||||||
|
import jp.juggler.util.data.notZero
|
||||||
import jp.juggler.util.int
|
import jp.juggler.util.int
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
import jp.juggler.util.log.withCaption
|
import jp.juggler.util.log.withCaption
|
||||||
import jp.juggler.util.media.createResizedBitmap
|
import jp.juggler.util.media.createResizedBitmap
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.ActivityResultHandler
|
||||||
|
import jp.juggler.util.ui.hideKeyboard
|
||||||
|
import jp.juggler.util.ui.isNotOk
|
||||||
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import jp.juggler.util.ui.vg
|
||||||
import org.jetbrains.anko.textColor
|
import org.jetbrains.anko.textColor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPickerDialogListener {
|
class ActColumnCustomize : AppCompatActivity(), View.OnClickListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -44,12 +59,6 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
|
|
||||||
internal const val EXTRA_COLUMN_INDEX = "column_index"
|
internal const val EXTRA_COLUMN_INDEX = "column_index"
|
||||||
|
|
||||||
internal const val COLOR_DIALOG_ID_HEADER_BACKGROUND = 1
|
|
||||||
internal const val COLOR_DIALOG_ID_HEADER_FOREGROUND = 2
|
|
||||||
internal const val COLOR_DIALOG_ID_COLUMN_BACKGROUND = 3
|
|
||||||
internal const val COLOR_DIALOG_ID_ACCT_TEXT = 4
|
|
||||||
internal const val COLOR_DIALOG_ID_CONTENT_TEXT = 5
|
|
||||||
|
|
||||||
internal const val PROGRESS_MAX = 65536
|
internal const val PROGRESS_MAX = 65536
|
||||||
|
|
||||||
fun createIntent(activity: ActMain, idx: Int) =
|
fun createIntent(activity: ActMain, idx: Int) =
|
||||||
|
@ -107,19 +116,13 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
|
|
||||||
val builder: ColorPickerDialog.Builder
|
|
||||||
|
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
|
|
||||||
R.id.btnHeaderBackgroundEdit -> {
|
R.id.btnHeaderBackgroundEdit -> launchAndShowError {
|
||||||
ColorPickerDialog.newBuilder()
|
column.headerBgColor = Color.BLACK or dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = column.getHeaderBackgroundColor(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = false,
|
||||||
.setShowAlphaSlider(false)
|
)
|
||||||
.setDialogId(COLOR_DIALOG_ID_HEADER_BACKGROUND)
|
|
||||||
.setColor(column.getHeaderBackgroundColor())
|
|
||||||
.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnHeaderBackgroundReset -> {
|
R.id.btnHeaderBackgroundReset -> {
|
||||||
|
@ -127,14 +130,11 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnHeaderTextEdit -> {
|
R.id.btnHeaderTextEdit -> launchAndShowError {
|
||||||
ColorPickerDialog.newBuilder()
|
column.headerFgColor = Color.BLACK or dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = column.getHeaderNameColor(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = false,
|
||||||
.setShowAlphaSlider(false)
|
)
|
||||||
.setDialogId(COLOR_DIALOG_ID_HEADER_FOREGROUND)
|
|
||||||
.setColor(column.getHeaderNameColor())
|
|
||||||
.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnHeaderTextReset -> {
|
R.id.btnHeaderTextReset -> {
|
||||||
|
@ -142,14 +142,11 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnColumnBackgroundColor -> {
|
R.id.btnColumnBackgroundColor -> launchAndShowError {
|
||||||
builder = ColorPickerDialog.newBuilder()
|
column.columnBgColor = Color.BLACK or dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = column.columnBgColor.notZero(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = false,
|
||||||
.setShowAlphaSlider(false)
|
)
|
||||||
.setDialogId(COLOR_DIALOG_ID_COLUMN_BACKGROUND)
|
|
||||||
if (column.columnBgColor != 0) builder.setColor(column.columnBgColor)
|
|
||||||
builder.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnColumnBackgroundColorReset -> {
|
R.id.btnColumnBackgroundColorReset -> {
|
||||||
|
@ -157,14 +154,11 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnAcctColor -> {
|
R.id.btnAcctColor -> launchAndShowError {
|
||||||
ColorPickerDialog.newBuilder()
|
column.acctColor = dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = column.getAcctColor(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = true,
|
||||||
.setShowAlphaSlider(true)
|
).notZero() ?: 1
|
||||||
.setDialogId(COLOR_DIALOG_ID_ACCT_TEXT)
|
|
||||||
.setColor(column.getAcctColor())
|
|
||||||
.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnAcctColorReset -> {
|
R.id.btnAcctColorReset -> {
|
||||||
|
@ -172,14 +166,11 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnContentColor -> {
|
R.id.btnContentColor -> launchAndShowError {
|
||||||
ColorPickerDialog.newBuilder()
|
column.contentColor = dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = column.getContentColor(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = true,
|
||||||
.setShowAlphaSlider(true)
|
).notZero() ?: 1
|
||||||
.setDialogId(COLOR_DIALOG_ID_CONTENT_TEXT)
|
|
||||||
.setColor(column.getContentColor())
|
|
||||||
.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnContentColorReset -> {
|
R.id.btnContentColorReset -> {
|
||||||
|
@ -203,28 +194,6 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
|
||||||
when (dialogId) {
|
|
||||||
COLOR_DIALOG_ID_HEADER_BACKGROUND ->
|
|
||||||
column.headerBgColor = Color.BLACK or newColor
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_HEADER_FOREGROUND ->
|
|
||||||
column.headerFgColor = Color.BLACK or newColor
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_COLUMN_BACKGROUND ->
|
|
||||||
column.columnBgColor = Color.BLACK or newColor
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_ACCT_TEXT ->
|
|
||||||
column.acctColor = newColor.notZero() ?: 1
|
|
||||||
|
|
||||||
COLOR_DIALOG_ID_CONTENT_TEXT ->
|
|
||||||
column.contentColor = newColor.notZero() ?: 1
|
|
||||||
}
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {}
|
|
||||||
|
|
||||||
private fun updateBackground(uriArg: Uri) {
|
private fun updateBackground(uriArg: Uri) {
|
||||||
launchMain {
|
launchMain {
|
||||||
var resultUri: String? = null
|
var resultUri: String? = null
|
||||||
|
|
|
@ -8,8 +8,7 @@ import android.media.RingtoneManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
import com.jrummyapps.android.colorpicker.dialogColorPicker
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
|
||||||
import jp.juggler.subwaytooter.databinding.ActHighlightEditBinding
|
import jp.juggler.subwaytooter.databinding.ActHighlightEditBinding
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
import jp.juggler.subwaytooter.table.HighlightWord
|
||||||
import jp.juggler.subwaytooter.table.daoHighlightWord
|
import jp.juggler.subwaytooter.table.daoHighlightWord
|
||||||
|
@ -23,12 +22,15 @@ import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
import jp.juggler.util.long
|
import jp.juggler.util.long
|
||||||
import jp.juggler.util.string
|
import jp.juggler.util.string
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.ActivityResultHandler
|
||||||
|
import jp.juggler.util.ui.attrColor
|
||||||
|
import jp.juggler.util.ui.decodeRingtonePickerResult
|
||||||
|
import jp.juggler.util.ui.isEnabledAlpha
|
||||||
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
import org.jetbrains.anko.textColor
|
import org.jetbrains.anko.textColor
|
||||||
|
|
||||||
class ActHighlightWordEdit
|
class ActHighlightWordEdit
|
||||||
: AppCompatActivity(),
|
: AppCompatActivity(),
|
||||||
ColorPickerDialogListener,
|
|
||||||
CompoundButton.OnCheckedChangeListener {
|
CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -141,20 +143,26 @@ class ActHighlightWordEdit
|
||||||
views.btnDiscard.setOnClickListener { finish() }
|
views.btnDiscard.setOnClickListener { finish() }
|
||||||
views.btnSave.setOnClickListener { save() }
|
views.btnSave.setOnClickListener { save() }
|
||||||
views.btnTextColorEdit.setOnClickListener {
|
views.btnTextColorEdit.setOnClickListener {
|
||||||
openColorPicker(
|
launchAndShowError {
|
||||||
COLOR_DIALOG_ID_TEXT,
|
item.color_fg = Color.BLACK or dialogColorPicker(
|
||||||
item.color_fg
|
colorInitial = item.color_fg.notZero(),
|
||||||
|
alphaEnabled = false,
|
||||||
)
|
)
|
||||||
|
showColor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
views.btnTextColorReset.setOnClickListener {
|
views.btnTextColorReset.setOnClickListener {
|
||||||
item.color_fg = 0
|
item.color_fg = 0
|
||||||
showColor()
|
showColor()
|
||||||
}
|
}
|
||||||
views.btnBackgroundColorEdit.setOnClickListener {
|
views.btnBackgroundColorEdit.setOnClickListener {
|
||||||
openColorPicker(
|
launchAndShowError {
|
||||||
COLOR_DIALOG_ID_BACKGROUND,
|
item.color_bg = dialogColorPicker(
|
||||||
item.color_bg
|
colorInitial = item.color_bg.notZero(),
|
||||||
)
|
alphaEnabled = true,
|
||||||
|
).notZero() ?: 0x01000000
|
||||||
|
showColor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
views.btnBackgroundColorReset.setOnClickListener {
|
views.btnBackgroundColorReset.setOnClickListener {
|
||||||
item.color_bg = 0
|
item.color_bg = 0
|
||||||
|
@ -180,28 +188,6 @@ class ActHighlightWordEdit
|
||||||
showSound()
|
showSound()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openColorPicker(id: Int, initialColor: Int) {
|
|
||||||
val builder = ColorPickerDialog.newBuilder()
|
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
|
||||||
.setAllowPresets(true)
|
|
||||||
.setShowAlphaSlider(id == COLOR_DIALOG_ID_BACKGROUND)
|
|
||||||
.setDialogId(id)
|
|
||||||
|
|
||||||
if (initialColor != 0) builder.setColor(initialColor)
|
|
||||||
|
|
||||||
builder.show(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {}
|
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, newColor: Int) {
|
|
||||||
when (dialogId) {
|
|
||||||
COLOR_DIALOG_ID_TEXT -> item.color_fg = newColor or Color.BLACK
|
|
||||||
COLOR_DIALOG_ID_BACKGROUND -> item.color_bg = newColor.notZero() ?: 0x01000000
|
|
||||||
}
|
|
||||||
showColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun showSound() {
|
private fun showSound() {
|
||||||
|
|
|
@ -2,15 +2,14 @@ package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
import com.jrummyapps.android.colorpicker.dialogColorPicker
|
||||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
|
||||||
import jp.juggler.subwaytooter.api.entity.Acct
|
import jp.juggler.subwaytooter.api.entity.Acct
|
||||||
import jp.juggler.subwaytooter.databinding.ActNicknameBinding
|
import jp.juggler.subwaytooter.databinding.ActNicknameBinding
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.AcctColor
|
||||||
|
@ -23,11 +22,17 @@ import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.data.notZero
|
import jp.juggler.util.data.notZero
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.string
|
import jp.juggler.util.string
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.ActivityResultHandler
|
||||||
|
import jp.juggler.util.ui.attrColor
|
||||||
|
import jp.juggler.util.ui.decodeRingtonePickerResult
|
||||||
|
import jp.juggler.util.ui.hideKeyboard
|
||||||
|
import jp.juggler.util.ui.isEnabledAlpha
|
||||||
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import jp.juggler.util.ui.vg
|
||||||
import org.jetbrains.anko.backgroundColor
|
import org.jetbrains.anko.backgroundColor
|
||||||
import org.jetbrains.anko.textColor
|
import org.jetbrains.anko.textColor
|
||||||
|
|
||||||
class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialogListener {
|
class ActNickname : AppCompatActivity(), View.OnClickListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LogCategory("ActNickname")
|
private val log = LogCategory("ActNickname")
|
||||||
|
@ -170,17 +175,14 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
val builder: ColorPickerDialog.Builder
|
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.btnTextColorEdit -> {
|
R.id.btnTextColorEdit -> launchAndShowError {
|
||||||
views.etNickname.hideKeyboard()
|
views.etNickname.hideKeyboard()
|
||||||
builder = ColorPickerDialog.newBuilder()
|
colorFg = Color.BLACK or dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = colorFg.notZero(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = false
|
||||||
.setShowAlphaSlider(false)
|
)
|
||||||
.setDialogId(1)
|
show()
|
||||||
if (colorFg != 0) builder.setColor(colorFg)
|
|
||||||
builder.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnTextColorReset -> {
|
R.id.btnTextColorReset -> {
|
||||||
|
@ -188,15 +190,13 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnBackgroundColorEdit -> {
|
R.id.btnBackgroundColorEdit -> launchAndShowError {
|
||||||
views.etNickname.hideKeyboard()
|
views.etNickname.hideKeyboard()
|
||||||
builder = ColorPickerDialog.newBuilder()
|
colorBg = Color.BLACK or dialogColorPicker(
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
colorInitial = colorBg.notZero(),
|
||||||
.setAllowPresets(true)
|
alphaEnabled = false,
|
||||||
.setShowAlphaSlider(false)
|
)
|
||||||
.setDialogId(2)
|
show()
|
||||||
if (colorBg != 0) builder.setColor(colorBg)
|
|
||||||
builder.show(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.btnBackgroundColorReset -> {
|
R.id.btnBackgroundColorReset -> {
|
||||||
|
@ -221,16 +221,6 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onColorSelected(dialogId: Int, @ColorInt newColor: Int) {
|
|
||||||
when (dialogId) {
|
|
||||||
1 -> colorFg = -0x1000000 or newColor
|
|
||||||
2 -> colorBg = -0x1000000 or newColor
|
|
||||||
}
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDialogDismissed(dialogId: Int) {}
|
|
||||||
|
|
||||||
private fun openNotificationSoundPicker() {
|
private fun openNotificationSoundPicker() {
|
||||||
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
package jp.juggler.subwaytooter
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import jp.juggler.subwaytooter.databinding.ActOssLicenseBinding
|
|
||||||
import jp.juggler.util.data.decodeUTF8
|
|
||||||
import jp.juggler.util.data.loadRawResource
|
|
||||||
import jp.juggler.util.log.LogCategory
|
|
||||||
import jp.juggler.util.ui.setNavigationBack
|
|
||||||
|
|
||||||
class ActOSSLicense : AppCompatActivity() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = LogCategory("ActOSSLicense")
|
|
||||||
}
|
|
||||||
|
|
||||||
private val views by lazy {
|
|
||||||
ActOssLicenseBinding.inflate(layoutInflater)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
App1.setActivityTheme(this)
|
|
||||||
setContentView(views.root)
|
|
||||||
setSupportActionBar(views.toolbar)
|
|
||||||
setNavigationBack(views.toolbar)
|
|
||||||
fixHorizontalMargin(views.svContent)
|
|
||||||
|
|
||||||
try {
|
|
||||||
views.tvText.text = loadRawResource(R.raw.oss_license).decodeUTF8()
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "can't show license text.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
@ -405,7 +406,7 @@ class App1 : Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActivityTheme(
|
fun setActivityTheme(
|
||||||
activity: AppCompatActivity,
|
activity: Activity,
|
||||||
forceDark: Boolean = false,
|
forceDark: Boolean = false,
|
||||||
) {
|
) {
|
||||||
prepare(activity.applicationContext, "setActivityTheme")
|
prepare(activity.applicationContext, "setActivityTheme")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
@ -469,7 +470,7 @@ fun ViewGroup.generateLayoutParamsEx(): ViewGroup.LayoutParams? =
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
|
fun Activity.setStatusBarColor(forceDark: Boolean = false) {
|
||||||
window?.apply {
|
window?.apply {
|
||||||
if (Build.VERSION.SDK_INT < 30) {
|
if (Build.VERSION.SDK_INT < 30) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
|
|
|
@ -28,7 +28,7 @@ import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.ActMutedApp
|
import jp.juggler.subwaytooter.ActMutedApp
|
||||||
import jp.juggler.subwaytooter.ActMutedPseudoAccount
|
import jp.juggler.subwaytooter.ActMutedPseudoAccount
|
||||||
import jp.juggler.subwaytooter.ActMutedWord
|
import jp.juggler.subwaytooter.ActMutedWord
|
||||||
import jp.juggler.subwaytooter.ActOSSLicense
|
import jp.juggler.subwaytooter.ui.ossLicense.ActOSSLicense
|
||||||
import jp.juggler.subwaytooter.ActPushMessageList
|
import jp.juggler.subwaytooter.ActPushMessageList
|
||||||
import jp.juggler.subwaytooter.App1
|
import jp.juggler.subwaytooter.App1
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
package jp.juggler.subwaytooter.ui.ossLicense
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
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.Spacer
|
||||||
|
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.items
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
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.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import jp.juggler.subwaytooter.App1
|
||||||
|
import jp.juggler.subwaytooter.R
|
||||||
|
import jp.juggler.subwaytooter.databinding.ActOssLicenseBinding
|
||||||
|
import jp.juggler.subwaytooter.util.openBrowser
|
||||||
|
import jp.juggler.subwaytooter.util.provideViewModel
|
||||||
|
import jp.juggler.subwaytooter.util.toAnnotatedString
|
||||||
|
import jp.juggler.util.data.mayUri
|
||||||
|
import jp.juggler.util.data.notEmpty
|
||||||
|
import jp.juggler.util.log.LogCategory
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class ActOSSLicense : ComponentActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LogCategory("ActOSSLicense")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val views by lazy {
|
||||||
|
ActOssLicenseBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
private val viewModel by lazy {
|
||||||
|
provideViewModel(this) {
|
||||||
|
ActOSSLicenseViewModel(application)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
App1.setActivityTheme(this)
|
||||||
|
setContent {
|
||||||
|
Screen(
|
||||||
|
librariesFlow = viewModel.libraries,
|
||||||
|
isProgressShownFlow = viewModel.isProgressShown,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
viewModel.load()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.e(ex, "dependency in fo loading failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun DefaultPreview() {
|
||||||
|
Screen(
|
||||||
|
isProgressShownFlow = MutableStateFlow(false),
|
||||||
|
librariesFlow = MutableStateFlow(
|
||||||
|
listOf(
|
||||||
|
LibText(
|
||||||
|
nameBig = "nameBig1".toAnnotatedString(),
|
||||||
|
nameSmall = "nameSmall".toAnnotatedString(),
|
||||||
|
desc = "desc".toAnnotatedString(),
|
||||||
|
),
|
||||||
|
LibText(
|
||||||
|
nameBig = "nameBig2".toAnnotatedString(),
|
||||||
|
nameSmall = "nameSmall".toAnnotatedString(),
|
||||||
|
desc = "desc".toAnnotatedString(),
|
||||||
|
),
|
||||||
|
LibText(
|
||||||
|
nameBig = "nameBig3".toAnnotatedString(),
|
||||||
|
nameSmall = "nameSmall".toAnnotatedString(),
|
||||||
|
desc = "desc".toAnnotatedString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun Screen(
|
||||||
|
librariesFlow: Flow<List<LibText>>,
|
||||||
|
isProgressShownFlow: Flow<Boolean>,
|
||||||
|
) {
|
||||||
|
val isProgressShown = isProgressShownFlow.collectAsState(false)
|
||||||
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.oss_license))
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { finish() }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
// imageVector = AutoMirrored.Outlined.ArrowBack,
|
||||||
|
imageVector = Icons.Outlined.Close,
|
||||||
|
contentDescription = stringResource(R.string.close)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
// containerColor = : Color = Color.Unspecified,
|
||||||
|
// scrolledContainerColor: Color = Color.Unspecified,
|
||||||
|
// navigationIconContentColor: Color = Color.Unspecified,
|
||||||
|
// titleContentColor: Color = Color.Unspecified,
|
||||||
|
// actionIconContentColor: Color = Color.Unspecified,
|
||||||
|
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
when (isProgressShown.value) {
|
||||||
|
true -> Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.width(64.dp),
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> ScrollContent(innerPadding, librariesFlow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScrollContent(
|
||||||
|
innerPadding: PaddingValues,
|
||||||
|
librariesFlow: Flow<List<LibText>>,
|
||||||
|
) {
|
||||||
|
val libraries = librariesFlow.collectAsState(emptyList())
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(innerPadding),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
|
||||||
|
) {
|
||||||
|
items(libraries.value) { LibTextCard(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LibTextCard(src: LibText) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 6.dp, horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
src.nameBig.notEmpty()?.let {
|
||||||
|
ClickableText(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
) { offset ->
|
||||||
|
it.getStringAnnotations(
|
||||||
|
tag = "URL",
|
||||||
|
start = offset,
|
||||||
|
end = offset,
|
||||||
|
).firstOrNull()?.let { a ->
|
||||||
|
openBrowser(a.item.mayUri())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src.nameSmall.notEmpty()?.let {
|
||||||
|
ClickableText(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
) { offset ->
|
||||||
|
it.getStringAnnotations(
|
||||||
|
tag = "URL",
|
||||||
|
start = offset,
|
||||||
|
end = offset,
|
||||||
|
).firstOrNull()?.let { a ->
|
||||||
|
openBrowser(a.item.mayUri())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
src.desc.notEmpty()?.let {
|
||||||
|
ClickableText(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
) { offset ->
|
||||||
|
it.getStringAnnotations(
|
||||||
|
tag = "URL",
|
||||||
|
start = offset,
|
||||||
|
end = offset,
|
||||||
|
).firstOrNull()?.let { a ->
|
||||||
|
openBrowser(a.item.mayUri())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,14 +3,10 @@ package jp.juggler.subwaytooter.util
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okio.*
|
import okio.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class ProgressResponseBody private constructor(
|
class ProgressResponseBody private constructor(
|
||||||
|
@ -18,24 +14,24 @@ class ProgressResponseBody private constructor(
|
||||||
) : ResponseBody() {
|
) : ResponseBody() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
internal val log = LogCategory("ProgressResponseBody")
|
internal val log = LogCategory("ProgressResponseBody")
|
||||||
|
|
||||||
fun makeInterceptor(): Interceptor = Interceptor { chain ->
|
// ProgressResponseBody を間に挟むインタセプタを作成する
|
||||||
|
fun makeInterceptor() = Interceptor { chain ->
|
||||||
val originalResponse = chain.proceed(chain.request())
|
val originalResponse = chain.proceed(chain.request())
|
||||||
originalResponse.newBuilder()
|
originalResponse.newBuilder()
|
||||||
.body(ProgressResponseBody(originalResponse.body))
|
.body(ProgressResponseBody(originalResponse.body))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 進捗コールバックつきでバイト列を読む
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun bytes(
|
fun bytes(
|
||||||
response: Response,
|
response: Response,
|
||||||
callback: suspend (bytesRead: Long, bytesTotal: Long) -> Unit,
|
callback: suspend (bytesRead: Long, bytesTotal: Long) -> Unit,
|
||||||
): ByteArray {
|
) = bytes(response.body, callback)
|
||||||
return bytes(response.body, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 進捗コールバックつきでバイト列を読む
|
||||||
@Suppress("MemberVisibilityCanPrivate")
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun bytes(
|
private fun bytes(
|
||||||
|
@ -49,204 +45,25 @@ class ProgressResponseBody private constructor(
|
||||||
|
|
||||||
private var callback: suspend (bytesRead: Long, bytesTotal: Long) -> Unit = { _, _ -> }
|
private var callback: suspend (bytesRead: Long, bytesTotal: Long) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
/*
|
private val wrappedSource by lazy {
|
||||||
RequestBody.bytes() is defined as final, We can't override it.
|
object : ForwardingSource(originalBody.source()) {
|
||||||
Make WrappedBufferedSource to capture BufferedSource.readByteArray().
|
var totalBytesRead = 0L
|
||||||
*/
|
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||||
|
val bytesRead = super.read(sink, byteCount)
|
||||||
private val wrappedSource: BufferedSource by lazy {
|
// read() returns the number of bytes read, or -1 if this source is exhausted.
|
||||||
val originalSource = originalBody.source()
|
totalBytesRead += max(0, bytesRead)
|
||||||
|
runBlocking {
|
||||||
try {
|
callback.invoke(
|
||||||
// if it is RealBufferedSource, I can access to source public field via reflection.
|
totalBytesRead,
|
||||||
val fieldSource = originalSource.javaClass.getField("source")
|
originalBody.contentLength(),
|
||||||
|
)
|
||||||
// If there is the method, create the wrapper.
|
|
||||||
object : ForwardingBufferedSource(originalSource) {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun readByteArray(): ByteArray {
|
|
||||||
/*
|
|
||||||
RealBufferedSource.readByteArray() does:
|
|
||||||
- buffer.writeAll(source);
|
|
||||||
- return buffer.readByteArray(buffer.size());
|
|
||||||
|
|
||||||
We do same things using Reflection, with progress.
|
|
||||||
*/
|
|
||||||
|
|
||||||
try {
|
|
||||||
val contentLength = originalBody.contentLength()
|
|
||||||
val buffer = originalSource.buffer
|
|
||||||
val source = fieldSource.get(originalSource) as Source?
|
|
||||||
?: throw IllegalArgumentException("source == null")
|
|
||||||
|
|
||||||
// same thing of Buffer.writeAll(), with counting.
|
|
||||||
var nRead: Long = 0
|
|
||||||
runBlocking { callback(0, max(contentLength, 1)) }
|
|
||||||
while (true) {
|
|
||||||
val delta = source.read(buffer, 8192)
|
|
||||||
if (delta == -1L) break
|
|
||||||
nRead += delta
|
|
||||||
if (nRead > 0) {
|
|
||||||
runBlocking { callback(nRead, max(contentLength, nRead)) }
|
|
||||||
}
|
}
|
||||||
|
return bytesRead
|
||||||
}
|
}
|
||||||
// EOS時の進捗
|
}.buffer()
|
||||||
runBlocking { callback(nRead, max(contentLength, nRead)) }
|
|
||||||
|
|
||||||
return buffer.readByteArray()
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "readByteArray() failed.")
|
|
||||||
return originalSource.readByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "can't access to RealBufferedSource#source field.")
|
|
||||||
originalSource
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
override fun contentType() = originalBody.contentType()
|
||||||
then you can read response body's bytes() with progress callback.
|
override fun contentLength() = originalBody.contentLength()
|
||||||
example:
|
override fun source() = wrappedSource
|
||||||
byte[] data = ProgressResponseBody.bytes( response, new ProgressResponseBody.Callback() {
|
|
||||||
@Override public void progressBytes( long bytesRead, long bytesTotal ){
|
|
||||||
publishApiProgressRatio( (int) bytesRead, (int) bytesTotal );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
*/
|
|
||||||
|
|
||||||
override fun contentType(): MediaType? {
|
|
||||||
return originalBody.contentType()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun contentLength(): Long {
|
|
||||||
return originalBody.contentLength()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun source(): BufferedSource = wrappedSource
|
|
||||||
|
|
||||||
// To avoid double buffering, We have to make ForwardingBufferedSource.
|
|
||||||
internal open class ForwardingBufferedSource(
|
|
||||||
private val originalSource: BufferedSource,
|
|
||||||
) : BufferedSource {
|
|
||||||
|
|
||||||
override val buffer: Buffer
|
|
||||||
get() = originalSource.buffer
|
|
||||||
|
|
||||||
@Deprecated("use val buffer.", replaceWith = ReplaceWith("buffer"))
|
|
||||||
@Suppress("OverridingDeprecatedMember")
|
|
||||||
override fun buffer() = buffer
|
|
||||||
|
|
||||||
override fun peek(): BufferedSource = originalSource.peek()
|
|
||||||
|
|
||||||
override fun read(dst: ByteBuffer?) = originalSource.read(dst)
|
|
||||||
|
|
||||||
override fun isOpen() = originalSource.isOpen
|
|
||||||
|
|
||||||
override fun exhausted() = originalSource.exhausted()
|
|
||||||
|
|
||||||
override fun require(byteCount: Long) = originalSource.require(byteCount)
|
|
||||||
|
|
||||||
override fun request(byteCount: Long) = originalSource.request(byteCount)
|
|
||||||
|
|
||||||
override fun readByte() = originalSource.readByte()
|
|
||||||
|
|
||||||
override fun readShort() = originalSource.readShort()
|
|
||||||
|
|
||||||
override fun readShortLe() = originalSource.readShortLe()
|
|
||||||
|
|
||||||
override fun readInt() = originalSource.readInt()
|
|
||||||
|
|
||||||
override fun readIntLe() = originalSource.readIntLe()
|
|
||||||
|
|
||||||
override fun readLong() = originalSource.readLong()
|
|
||||||
|
|
||||||
override fun readLongLe() = originalSource.readLongLe()
|
|
||||||
|
|
||||||
override fun readDecimalLong() = originalSource.readDecimalLong()
|
|
||||||
|
|
||||||
override fun readHexadecimalUnsignedLong() = originalSource.readHexadecimalUnsignedLong()
|
|
||||||
|
|
||||||
override fun skip(byteCount: Long) = originalSource.skip(byteCount)
|
|
||||||
|
|
||||||
override fun readByteString(): ByteString = originalSource.readByteString()
|
|
||||||
|
|
||||||
override fun readByteString(byteCount: Long): ByteString =
|
|
||||||
originalSource.readByteString(byteCount)
|
|
||||||
|
|
||||||
override fun select(options: Options) = originalSource.select(options)
|
|
||||||
|
|
||||||
override fun readByteArray(): ByteArray = originalSource.readByteArray()
|
|
||||||
|
|
||||||
override fun readByteArray(byteCount: Long): ByteArray =
|
|
||||||
originalSource.readByteArray(byteCount)
|
|
||||||
|
|
||||||
override fun read(sink: ByteArray) = originalSource.read(sink)
|
|
||||||
|
|
||||||
override fun readFully(sink: ByteArray) = originalSource.readFully(sink)
|
|
||||||
|
|
||||||
override fun read(sink: ByteArray, offset: Int, byteCount: Int) =
|
|
||||||
originalSource.read(sink, offset, byteCount)
|
|
||||||
|
|
||||||
override fun readFully(sink: Buffer, byteCount: Long) =
|
|
||||||
originalSource.readFully(sink, byteCount)
|
|
||||||
|
|
||||||
override fun readAll(sink: Sink) = originalSource.readAll(sink)
|
|
||||||
|
|
||||||
override fun readUtf8(): String = originalSource.readUtf8()
|
|
||||||
|
|
||||||
override fun readUtf8(byteCount: Long): String = originalSource.readUtf8(byteCount)
|
|
||||||
|
|
||||||
override fun readUtf8Line(): String? = originalSource.readUtf8Line()
|
|
||||||
|
|
||||||
override fun readUtf8LineStrict(): String = originalSource.readUtf8LineStrict()
|
|
||||||
|
|
||||||
override fun readUtf8LineStrict(limit: Long): String =
|
|
||||||
originalSource.readUtf8LineStrict(limit)
|
|
||||||
|
|
||||||
override fun readUtf8CodePoint() = originalSource.readUtf8CodePoint()
|
|
||||||
|
|
||||||
override fun readString(charset: Charset): String = originalSource.readString(charset)
|
|
||||||
|
|
||||||
override fun readString(byteCount: Long, charset: Charset): String =
|
|
||||||
originalSource.readString(byteCount, charset)
|
|
||||||
|
|
||||||
override fun indexOf(b: Byte) = originalSource.indexOf(b)
|
|
||||||
|
|
||||||
override fun indexOf(b: Byte, fromIndex: Long) = originalSource.indexOf(b, fromIndex)
|
|
||||||
|
|
||||||
override fun indexOf(b: Byte, fromIndex: Long, toIndex: Long) =
|
|
||||||
originalSource.indexOf(b, fromIndex, toIndex)
|
|
||||||
|
|
||||||
override fun indexOf(bytes: ByteString) = originalSource.indexOf(bytes)
|
|
||||||
|
|
||||||
override fun indexOf(bytes: ByteString, fromIndex: Long) =
|
|
||||||
originalSource.indexOf(bytes, fromIndex)
|
|
||||||
|
|
||||||
override fun indexOfElement(targetBytes: ByteString) =
|
|
||||||
originalSource.indexOfElement(targetBytes)
|
|
||||||
|
|
||||||
override fun indexOfElement(targetBytes: ByteString, fromIndex: Long) =
|
|
||||||
originalSource.indexOfElement(targetBytes, fromIndex)
|
|
||||||
|
|
||||||
override fun rangeEquals(offset: Long, bytes: ByteString) =
|
|
||||||
originalSource.rangeEquals(offset, bytes)
|
|
||||||
|
|
||||||
override fun rangeEquals(
|
|
||||||
offset: Long,
|
|
||||||
bytes: ByteString,
|
|
||||||
bytesOffset: Int,
|
|
||||||
byteCount: Int,
|
|
||||||
) = originalSource.rangeEquals(offset, bytes, bytesOffset, byteCount)
|
|
||||||
|
|
||||||
override fun inputStream(): InputStream = originalSource.inputStream()
|
|
||||||
|
|
||||||
override fun read(sink: Buffer, byteCount: Long) = originalSource.read(sink, byteCount)
|
|
||||||
|
|
||||||
override fun timeout(): Timeout = originalSource.timeout()
|
|
||||||
|
|
||||||
override fun close() = originalSource.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
====================================================
|
|
||||||
tateisu/SubwayTooter
|
|
||||||
https://github.com/tateisu/SubwayTooter
|
|
||||||
|
|
||||||
Copyright (C) 2017 tateisu
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
square/okhttp
|
|
||||||
https://github.com/square/okhttp
|
|
||||||
|
|
||||||
Copyright (C) 2012 Square, Inc.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
Commons IO
|
|
||||||
https://commons.apache.org/proper/commons-io/
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
chrisjenx/Calligraphy
|
|
||||||
https://github.com/chrisjenx/Calligraphy
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
woxblom/DragListView
|
|
||||||
https://github.com/woxblom/DragListView
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
omadahealth/SwipyRefreshLayout
|
|
||||||
https://github.com/omadahealth/SwipyRefreshLayout
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 OrangeGangsters
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
====================================================
|
|
||||||
kenglxn/QRGen
|
|
||||||
https://github.com/kenglxn/QRGen
|
|
||||||
|
|
||||||
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.
|
|
||||||
====================================================
|
|
||||||
sephiroth74/Android-Exif-Extended
|
|
||||||
https://github.com/sephiroth74/Android-Exif-Extended
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
jrummyapps/colorpicker
|
|
||||||
https://github.com/jrummyapps/colorpicker
|
|
||||||
|
|
||||||
Copyright (C) 2017 Jared Rummler
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
bumptech/glide
|
|
||||||
https://github.com/bumptech/glide
|
|
||||||
|
|
||||||
Copyright 2014 Google, Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are
|
|
||||||
permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
|
||||||
conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
||||||
of conditions and the following disclaimer in the documentation and/or other materials
|
|
||||||
provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
The views and conclusions contained in the software and documentation are those of the
|
|
||||||
authors and should not be interpreted as representing official policies, either expressed
|
|
||||||
or implied, of Google, Inc.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
iamcal/emoji-data
|
|
||||||
https://github.com/iamcal/emoji-data
|
|
||||||
|
|
||||||
Copyright (c) 2013 Cal Henderson
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
twitter/twemoji
|
|
||||||
https://github.com/twitter/twemoji
|
|
||||||
|
|
||||||
Copyright (c) 2014 Twitter, Inc and other contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
google/ExoPlayer
|
|
||||||
https://github.com/google/ExoPlayer
|
|
||||||
|
|
||||||
Copyright (C) 2018 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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
Icons8
|
|
||||||
https://icons8.com/license/
|
|
||||||
https://icons8.com/articles/weve-contributed-an-icon-to-imageoptim/
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
OpenSticker
|
|
||||||
https://opensticker.0px.io/
|
|
||||||
|
|
||||||
(c) 2020 weep, kyori19, cutls
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
BigBadaboom/AndroidSVG
|
|
||||||
https://github.com/BigBadaboom/androidsvg
|
|
||||||
|
|
||||||
Copyright 2014 Paul LeBeau, Cave Rock Software Ltd.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
====================================================
|
|
||||||
google/Conscrypt
|
|
||||||
https://github.com/google/conscrypt
|
|
||||||
|
|
||||||
Copyright (C) 2017 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.
|
|
||||||
|
|
||||||
====================================================
|
|
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,14 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
import org.junit.Assert.*
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun addition_isCorrect() {
|
||||||
assertEquals(4, 2 + 2)
|
assertEquals(
|
||||||
|
expected = 4,
|
||||||
|
actual = 2 + 2,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import kotlin.test.Test
|
||||||
import org.junit.Test
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class TestArrayListSizeBug {
|
class TestArrayListSizeBug {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testArrayListSize() {
|
fun testArrayListSize() {
|
||||||
val list = ArrayList(arrayOf("c", "b", "a").toList())
|
val list = ArrayList(arrayOf("c", "b", "a").toList())
|
||||||
assertEquals("ArrayList size", 3, list.size)
|
assertEquals(
|
||||||
|
expected = 3,
|
||||||
|
actual = list.size,
|
||||||
|
message = "ArrayList size",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayListDerived<E>(args: List<E>) : ArrayList<E>(args)
|
class ArrayListDerived<E>(args: List<E>) : ArrayList<E>(args)
|
||||||
|
@ -16,7 +20,11 @@ class TestArrayListSizeBug {
|
||||||
@Test
|
@Test
|
||||||
fun testArrayListDerived() {
|
fun testArrayListDerived() {
|
||||||
val list = ArrayListDerived(arrayOf("c", "b", "a").toList())
|
val list = ArrayListDerived(arrayOf("c", "b", "a").toList())
|
||||||
assertEquals("ArrayListDerived size", 3, list.size)
|
assertEquals(
|
||||||
|
expected = 3,
|
||||||
|
actual = list.size,
|
||||||
|
message = "ArrayListDerived size",
|
||||||
|
)
|
||||||
// kotlin 1.5.31で(Javaの) size() ではなく getSize() にアクセスしようとして例外を出していた
|
// kotlin 1.5.31で(Javaの) size() ではなく getSize() にアクセスしようとして例外を出していた
|
||||||
// kotlin 1.5.30では大丈夫だったが、JetPack Composeは 1.5.31を要求するのだった…。
|
// kotlin 1.5.30では大丈夫だったが、JetPack Composeは 1.5.31を要求するのだった…。
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import org.junit.Assert.assertEquals
|
import kotlin.test.Test
|
||||||
import org.junit.Test
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class TestColumnMeta {
|
class TestColumnMeta {
|
||||||
|
|
||||||
|
@ -12,8 +12,12 @@ class TestColumnMeta {
|
||||||
val actual = columnList.createTableSql()
|
val actual = columnList.createTableSql()
|
||||||
.joinToString(";")
|
.joinToString(";")
|
||||||
val expect =
|
val expect =
|
||||||
"create table if not exists access_info (_id INTEGER PRIMARY KEY,a text not null,confirm_boost integer default 1,confirm_favourite integer default 1,confirm_follow integer default 1,confirm_follow_locked integer default 1,confirm_post integer default 1,confirm_reaction integer default 1,confirm_unbookmark integer default 1,confirm_unboost integer default 1,confirm_unfavourite integer default 1,confirm_unfollow integer default 1,d text,default_sensitive integer default 0,default_text text default '',dont_hide_nsfw integer default 0,dont_show_timeout integer default 0,expand_cw integer default 0,extra_json text default null,h text not null,image_max_megabytes text default null,image_resize text default null,is_misskey integer default 0,last_notification_error text,last_push_endpoint text,last_subscription_error text,max_toot_chars integer default 0,movie_max_megabytes text default null,notification_boost integer default 1,notification_favourite integer default 1,notification_follow integer default 1,notification_follow_request integer default 1,notification_mention integer default 1,notification_post integer default 1,notification_reaction integer default 1,notification_server text default '',notification_update integer default 1,notification_vote integer default 1,push_policy text default null,register_key text default '',register_time integer default 0,sound_uri text default '',t text not null,u text not null,visibility text);create index if not exists access_info_user on access_info(u);create index if not exists access_info_host on access_info(h,u)"
|
"create table if not exists access_info (_id INTEGER PRIMARY KEY,a text not null,confirm_boost integer default 1,confirm_favourite integer default 1,confirm_follow integer default 1,confirm_follow_locked integer default 1,confirm_post integer default 1,confirm_reaction integer default 1,confirm_unbookmark integer default 1,confirm_unboost integer default 1,confirm_unfavourite integer default 1,confirm_unfollow integer default 1,d text,default_sensitive integer default 0,default_text text default '',dont_hide_nsfw integer default 0,dont_show_timeout integer default 0,expand_cw integer default 0,extra_json text default null,h text not null,image_max_megabytes text default null,image_resize text default null,is_misskey integer default 0,max_toot_chars integer default 0,movie_max_megabytes text default null,notification_boost integer default 1,notification_favourite integer default 1,notification_follow integer default 1,notification_follow_request integer default 1,notification_mention integer default 1,notification_post integer default 1,notification_reaction integer default 1,notification_server text default '',notification_update integer default 1,notification_vote integer default 1,push_policy text default null,t text not null,u text not null,visibility text);create index if not exists access_info_user on access_info(u);create index if not exists access_info_host on access_info(h,u)"
|
||||||
assertEquals("SavedAccount createParams()", expect, actual)
|
assertEquals(
|
||||||
|
expected = expect,
|
||||||
|
actual = actual,
|
||||||
|
message = "SavedAccount createParams()",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -23,8 +27,8 @@ class TestColumnMeta {
|
||||||
2 to "alter table access_info add column notification_boost integer default 1;alter table access_info add column notification_favourite integer default 1;alter table access_info add column notification_follow integer default 1;alter table access_info add column notification_mention integer default 1",
|
2 to "alter table access_info add column notification_boost integer default 1;alter table access_info add column notification_favourite integer default 1;alter table access_info add column notification_follow integer default 1;alter table access_info add column notification_mention integer default 1",
|
||||||
10 to "alter table access_info add column confirm_follow integer default 1;alter table access_info add column confirm_follow_locked integer default 1;alter table access_info add column confirm_post integer default 1;alter table access_info add column confirm_unfollow integer default 1",
|
10 to "alter table access_info add column confirm_follow integer default 1;alter table access_info add column confirm_follow_locked integer default 1;alter table access_info add column confirm_post integer default 1;alter table access_info add column confirm_unfollow integer default 1",
|
||||||
13 to "alter table access_info add column notification_server text default ''",
|
13 to "alter table access_info add column notification_server text default ''",
|
||||||
14 to "alter table access_info add column register_key text default '';alter table access_info add column register_time integer default 0",
|
//使われなくなった 14 to "alter table access_info add column register_key text default '';alter table access_info add column register_time integer default 0",
|
||||||
16 to "alter table access_info add column sound_uri text default ''",
|
//使われなくなった 16 to "alter table access_info add column sound_uri text default ''",
|
||||||
18 to "alter table access_info add column dont_show_timeout integer default 0",
|
18 to "alter table access_info add column dont_show_timeout integer default 0",
|
||||||
23 to "alter table access_info add column confirm_favourite integer default 1",
|
23 to "alter table access_info add column confirm_favourite integer default 1",
|
||||||
24 to "alter table access_info add column confirm_unboost integer default 1;alter table access_info add column confirm_unfavourite integer default 1",
|
24 to "alter table access_info add column confirm_unboost integer default 1;alter table access_info add column confirm_unfavourite integer default 1",
|
||||||
|
@ -33,21 +37,28 @@ class TestColumnMeta {
|
||||||
33 to "alter table access_info add column notification_reaction integer default 1;alter table access_info add column notification_vote integer default 1",
|
33 to "alter table access_info add column notification_reaction integer default 1;alter table access_info add column notification_vote integer default 1",
|
||||||
38 to "alter table access_info add column default_sensitive integer default 0;alter table access_info add column expand_cw integer default 0",
|
38 to "alter table access_info add column default_sensitive integer default 0;alter table access_info add column expand_cw integer default 0",
|
||||||
39 to "alter table access_info add column max_toot_chars integer default 0",
|
39 to "alter table access_info add column max_toot_chars integer default 0",
|
||||||
42 to "alter table access_info add column last_notification_error text",
|
//使われなくなった 42 to "alter table access_info add column last_notification_error text",
|
||||||
44 to "alter table access_info add column notification_follow_request integer default 1",
|
44 to "alter table access_info add column notification_follow_request integer default 1",
|
||||||
45 to "alter table access_info add column last_subscription_error text",
|
//使われなくなった 45 to "alter table access_info add column last_subscription_error text",
|
||||||
46 to "alter table access_info add column last_push_endpoint text",
|
//使われなくなった 46 to "alter table access_info add column last_push_endpoint text",
|
||||||
56 to "alter table access_info add column d text",
|
56 to "alter table access_info add column d text",
|
||||||
57 to "alter table access_info add column notification_post integer default 1",
|
57 to "alter table access_info add column notification_post integer default 1",
|
||||||
59 to "alter table access_info add column image_max_megabytes text default null;alter table access_info add column image_resize text default null;alter table access_info add column movie_max_megabytes text default null",
|
59 to "alter table access_info add column image_max_megabytes text default null;alter table access_info add column image_resize text default null;alter table access_info add column movie_max_megabytes text default null",
|
||||||
60 to "alter table access_info add column push_policy text default null",
|
60 to "alter table access_info add column push_policy text default null",
|
||||||
61 to "alter table access_info add column confirm_reaction integer default 1",
|
61 to "alter table access_info add column confirm_reaction integer default 1",
|
||||||
|
62 to "alter table access_info add column confirm_unbookmark integer default 1",
|
||||||
|
63 to "alter table access_info add column extra_json text default null",
|
||||||
|
64 to "alter table access_info add column notification_update integer default 1",
|
||||||
)
|
)
|
||||||
for (newVersion in 1..expectMap.maxOf { it.key }) {
|
for (newVersion in 1..expectMap.maxOf { it.key }) {
|
||||||
val actualSql = columnList.upgradeSql(db = null, newVersion - 1, newVersion)
|
val actualSql = columnList.upgradeSql(db = null, newVersion - 1, newVersion)
|
||||||
.joinToString(";")
|
.joinToString(";")
|
||||||
val expectSql = expectMap[newVersion] ?: ""
|
val expectSql = expectMap[newVersion] ?: ""
|
||||||
assertEquals("SavedAccount v$newVersion", expectSql, actualSql)
|
assertEquals(
|
||||||
|
expected = expectSql,
|
||||||
|
actual = actualSql,
|
||||||
|
message = "SavedAccount v$newVersion",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
import java.net.IDN
|
import java.net.IDN
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@Suppress("MemberNameEqualsClassName")
|
@Suppress("MemberNameEqualsClassName")
|
||||||
class TestIDN {
|
class TestIDN {
|
||||||
|
@ -11,22 +11,49 @@ class TestIDN {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testIDN() {
|
fun testIDN() {
|
||||||
// normal conversion
|
// normal conversion
|
||||||
assertEquals("xn--3-pfuzbe6htf.juggler.jp", IDN.toASCII("マストドン3.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
assertEquals(
|
||||||
assertEquals("マストドン3.juggler.jp", IDN.toUnicode("xn--3-pfuzbe6htf.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
expected = "xn--3-pfuzbe6htf.juggler.jp",
|
||||||
|
actual = IDN.toASCII("マストドン3.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
expected = "マストドン3.juggler.jp",
|
||||||
|
actual = IDN.toUnicode("xn--3-pfuzbe6htf.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
|
||||||
// not IDN domain
|
// not IDN domain
|
||||||
assertEquals("mastodon.juggler.jp", IDN.toASCII("mastodon.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
assertEquals(
|
||||||
assertEquals("mastodon.juggler.jp", IDN.toUnicode("mastodon.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
expected = "mastodon.juggler.jp",
|
||||||
|
actual = IDN.toASCII("mastodon.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"mastodon.juggler.jp",
|
||||||
|
actual = IDN.toUnicode("mastodon.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
|
||||||
// 既に変換済みの引数
|
// 既に変換済みの引数
|
||||||
assertEquals("xn--3-pfuzbe6htf.juggler.jp", IDN.toASCII("xn--3-pfuzbe6htf.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
assertEquals(
|
||||||
assertEquals("マストドン3.juggler.jp", IDN.toUnicode("マストドン3.juggler.jp", IDN.ALLOW_UNASSIGNED))
|
expected = "xn--3-pfuzbe6htf.juggler.jp",
|
||||||
|
actual = IDN.toASCII("xn--3-pfuzbe6htf.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
expected = "マストドン3.juggler.jp",
|
||||||
|
actual = IDN.toUnicode("マストドン3.juggler.jp", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
|
||||||
// 複数のpunycode
|
// 複数のpunycode
|
||||||
assertEquals("թութ.հայ", IDN.toUnicode("xn--69aa8bzb.xn--y9a3aq", IDN.ALLOW_UNASSIGNED))
|
assertEquals(
|
||||||
|
expected = "թութ.հայ",
|
||||||
|
actual = IDN.toUnicode("xn--69aa8bzb.xn--y9a3aq", IDN.ALLOW_UNASSIGNED)
|
||||||
|
)
|
||||||
|
|
||||||
// ?
|
// ?
|
||||||
assertEquals("?", IDN.toASCII("?", IDN.ALLOW_UNASSIGNED))
|
assertEquals(
|
||||||
assertEquals("?", IDN.toUnicode("?", IDN.ALLOW_UNASSIGNED))
|
expected = "?",
|
||||||
|
actual = IDN.toASCII("?", IDN.ALLOW_UNASSIGNED),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
expected = "?",
|
||||||
|
actual = IDN.toUnicode("?", IDN.ALLOW_UNASSIGNED),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.buildJsonArray
|
||||||
import org.junit.Assert.assertEquals
|
import jp.juggler.util.data.buildJsonObject
|
||||||
import org.junit.Test
|
import jp.juggler.util.data.decodeJsonObject
|
||||||
|
import jp.juggler.util.data.decodeJsonValue
|
||||||
|
import jp.juggler.util.data.isDecimalNotation
|
||||||
|
import jp.juggler.util.data.jsonObjectOf
|
||||||
|
import jp.juggler.util.data.writeJsonValue
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class TestJson {
|
class TestJson {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -103,7 +109,11 @@ class TestJson {
|
||||||
fun x(expect: Boolean, n: Number) {
|
fun x(expect: Boolean, n: Number) {
|
||||||
val encoded = n.toString()
|
val encoded = n.toString()
|
||||||
val actual = encoded.isDecimalNotation()
|
val actual = encoded.isDecimalNotation()
|
||||||
assertEquals("${n.javaClass.simpleName} $n", expect, actual)
|
assertEquals(
|
||||||
|
expected = expect,
|
||||||
|
actual = actual,
|
||||||
|
message = "${n.javaClass.simpleName} $n",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// integers and longs
|
// integers and longs
|
||||||
x(false, 0)
|
x(false, 0)
|
||||||
|
@ -141,8 +151,16 @@ class TestJson {
|
||||||
val encodedObject = jsonObjectOf("n" to n).toString()
|
val encodedObject = jsonObjectOf("n" to n).toString()
|
||||||
val decodedObject = encodedObject.decodeJsonObject()
|
val decodedObject = encodedObject.decodeJsonObject()
|
||||||
val decoded = decodedObject["n"]
|
val decoded = decodedObject["n"]
|
||||||
assertEquals("$n type $encodedObject", expectClass, decoded?.javaClass)
|
assertEquals<Class<*>?>(
|
||||||
assertEquals("$n value $encodedObject", expectValue, decoded)
|
expected = expectClass,
|
||||||
|
actual = decoded?.javaClass,
|
||||||
|
message = "$n type $encodedObject",
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
expected = expectValue,
|
||||||
|
actual = decoded,
|
||||||
|
message = "$n value $encodedObject",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
x(0)
|
x(0)
|
||||||
x(0f, expectValue = 0)
|
x(0f, expectValue = 0)
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
@file:Suppress(
|
|
||||||
"USELESS_CAST", "unused", "DEPRECATED_IDENTITY_EQUALS", "UNUSED_VARIABLE",
|
|
||||||
"UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "VARIABLE_WITH_REDUNDANT_INITIALIZER",
|
|
||||||
"ReplaceCallWithComparison"
|
|
||||||
)
|
|
||||||
|
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
//import kotlin.test.*
|
//import kotlin.test.*
|
||||||
|
|
||||||
typealias TestLambdaCallback = (x: Int) -> Int
|
typealias TestLambdaCallback = (x: Int) -> Int
|
||||||
|
|
||||||
|
@Suppress(
|
||||||
|
"unused", "UNUSED_VARIABLE",
|
||||||
|
"UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE",
|
||||||
|
"ReplaceCallWithComparison"
|
||||||
|
)
|
||||||
class TestKotlinFeature {
|
class TestKotlinFeature {
|
||||||
|
|
||||||
private val CODE_A = 1
|
private val CODE_A = 1
|
||||||
|
|
|
@ -2,8 +2,8 @@ package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||||
import jp.juggler.util.data.asciiPatternString
|
import jp.juggler.util.data.asciiPatternString
|
||||||
import org.junit.Assert.assertEquals
|
import kotlin.test.Test
|
||||||
import org.junit.Test
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@Suppress("MemberNameEqualsClassName")
|
@Suppress("MemberNameEqualsClassName")
|
||||||
class TestMisskeyMention {
|
class TestMisskeyMention {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.util.PostImpl
|
import jp.juggler.subwaytooter.util.PostImpl
|
||||||
import org.junit.Assert.assertFalse
|
import kotlin.test.Test
|
||||||
import org.junit.Assert.assertTrue
|
import kotlin.test.assertFalse
|
||||||
import org.junit.Test
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class TestPostImplTagAscii {
|
class TestPostImplTagAscii {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
@file:Suppress(
|
|
||||||
"USELESS_CAST", "unused", "DEPRECATED_IDENTITY_EQUALS", "UNUSED_VARIABLE",
|
|
||||||
"UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "VARIABLE_WITH_REDUNDANT_INITIALIZER",
|
|
||||||
"ReplaceCallWithComparison"
|
|
||||||
)
|
|
||||||
|
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.util.VersionString
|
import jp.juggler.subwaytooter.util.VersionString
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
|
@Suppress(
|
||||||
|
"unused",
|
||||||
|
"ReplaceCallWithComparison"
|
||||||
|
)
|
||||||
class TestVersionString {
|
class TestVersionString {
|
||||||
@Test
|
@Test
|
||||||
fun test1() {
|
fun test1() {
|
||||||
|
|
|
@ -69,107 +69,122 @@ dependencies {
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
||||||
|
|
||||||
api("androidx.appcompat:appcompat:${Vers.appcompatVersion}")
|
// JugglerBaseInitializer で使う
|
||||||
api("androidx.browser:browser:1.8.0")
|
implementation("androidx.startup:startup-runtime:${Vers.androidxStartup}")
|
||||||
api("androidx.core:core-ktx:${Vers.coreKtxVersion}")
|
|
||||||
api("androidx.drawerlayout:drawerlayout:1.2.0")
|
|
||||||
api("androidx.emoji2:emoji2-bundled:${Vers.emoji2Version}")
|
|
||||||
api("androidx.emoji2:emoji2-views-helper:${Vers.emoji2Version}")
|
|
||||||
api("androidx.emoji2:emoji2-views:${Vers.emoji2Version}")
|
|
||||||
api("androidx.emoji2:emoji2:${Vers.emoji2Version}")
|
|
||||||
api("androidx.exifinterface:exifinterface:1.3.7")
|
|
||||||
api("androidx.lifecycle:lifecycle-common-java8:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-livedata-ktx:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-process:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-reactivestreams-ktx:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-runtime-ktx:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-service:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-viewmodel-ktx:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Vers.lifecycleVersion}")
|
|
||||||
api("androidx.recyclerview:recyclerview:1.3.2")
|
|
||||||
api("androidx.startup:startup-runtime:${Vers.startupVersion}")
|
|
||||||
api("androidx.work:work-runtime-ktx:${Vers.workVersion}")
|
|
||||||
api("androidx.work:work-runtime:${Vers.workVersion}")
|
|
||||||
api("com.astuetz:pagerslidingtabstrip:1.0.1")
|
|
||||||
api("com.caverock:androidsvg-aar:1.4")
|
|
||||||
api("com.github.hadilq:live-event:1.3.0")
|
|
||||||
api("com.github.omadahealth:swipy:1.2.3@aar")
|
|
||||||
api("com.github.woxthebox:draglistview:1.7.3")
|
|
||||||
api("com.google.android.flexbox:flexbox:3.0.0")
|
|
||||||
api("com.google.android.material:material:${Vers.materialVersion}")
|
|
||||||
api("com.otaliastudios:transcoder:0.10.5")
|
|
||||||
api("com.squareup.okhttp3:okhttp-urlconnection:${Vers.okhttpVersion}")
|
|
||||||
api("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}")
|
|
||||||
api("org.bouncycastle:bcprov-jdk15on:1.70")
|
|
||||||
api("org.jetbrains.kotlin:kotlin-reflect:${Vers.kotlinVersion}")
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:${Vers.kotlinxSerializationLibVersion}")
|
|
||||||
api("ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0")
|
|
||||||
|
|
||||||
//non-OSS dependency api "androidx.media3:media3-cast:$media3Version"
|
// decodeP256dh で使う
|
||||||
api("androidx.media3:media3-common:${Vers.media3Version}")
|
implementation("org.bouncycastle:bcprov-jdk15on:1.70")
|
||||||
api("androidx.media3:media3-datasource:${Vers.media3Version}")
|
|
||||||
api("androidx.media3:media3-effect:${Vers.media3Version}")
|
|
||||||
api("androidx.media3:media3-exoplayer:${Vers.media3Version}")
|
|
||||||
api("androidx.media3:media3-session:${Vers.media3Version}")
|
|
||||||
api("androidx.media3:media3-transformer:${Vers.media3Version}")
|
|
||||||
api("androidx.media3:media3-ui:${Vers.media3Version}")
|
|
||||||
|
|
||||||
// commons-codecをapiにすると、端末上の古いjarが使われてしまう
|
// defaultSecurityProvider で使う
|
||||||
// declaration of "org.apache.commons.codec.binary.Base64" appears in /system/framework/org.apache.http.legacy.jar)
|
implementation("org.conscrypt:conscrypt-android:${Vers.conscryptVersion}")
|
||||||
androidTestImplementation("commons-codec:commons-codec:${Vers.commonsCodecVersion}")
|
|
||||||
|
|
||||||
// Koin main features for Android
|
// AudioTranscoderで使う
|
||||||
api("io.insert-koin:koin-android:${Vers.koinVersion}")
|
implementation("androidx.media3:media3-common:${Vers.androidxMedia3}")
|
||||||
api("io.insert-koin:koin-android-compat:${Vers.koinVersion}")
|
implementation("androidx.media3:media3-transformer:${Vers.androidxMedia3}")
|
||||||
api("io.insert-koin:koin-androidx-workmanager:${Vers.koinVersion}")
|
implementation("androidx.media3:media3-effect:${Vers.androidxMedia3}")
|
||||||
// api( "io.insert-koin:koin-androidx-navigation:$koinVersion")
|
|
||||||
// api( "io.insert-koin:koin-androidx-compose:$koinVersion")
|
|
||||||
|
|
||||||
// for com.bumptech.glide.integration.webp.*
|
// EmptyScope.kt で使う
|
||||||
api("com.github.zjupure:webpdecoder:${Vers.webpDecoderVersion}")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Vers.kotlinxCoroutinesVersion}")
|
||||||
api("com.github.bumptech.glide:glide:${Vers.glideVersion}")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:${Vers.androidxLifecycle}")
|
||||||
api("com.github.bumptech.glide:annotations:${Vers.glideVersion}")
|
|
||||||
api("com.github.bumptech.glide:okhttp3-integration:${Vers.glideVersion}") {
|
|
||||||
exclude("com.squareup.okhttp3", "okhttp")
|
|
||||||
}
|
|
||||||
|
|
||||||
androidTestApi("androidx.test.espresso:espresso-core:${Vers.androidxTestEspressoCoreVersion}")
|
// Compat.kt で使う
|
||||||
androidTestApi("androidx.test.ext:junit-ktx:1.1.5")
|
implementation("androidx.annotation:annotation:${Vers.androidxAnnotation}")
|
||||||
androidTestApi("androidx.test.ext:junit:${Vers.androidxTestExtJunitVersion}")
|
implementation("androidx.appcompat:appcompat:${Vers.androidxAppcompat}")
|
||||||
androidTestApi("androidx.test.ext:truth:1.5.0")
|
|
||||||
androidTestApi("androidx.test:core-ktx:${Vers.testKtxVersion}")
|
|
||||||
androidTestApi("androidx.test:core:${Vers.androidxTestVersion}")
|
|
||||||
androidTestApi("androidx.test:runner:1.5.2")
|
|
||||||
androidTestApi("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
|
||||||
androidTestApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
testApi("androidx.arch.core:core-testing:${Vers.archVersion}")
|
|
||||||
testApi("junit:junit:${Vers.junitVersion}")
|
|
||||||
testApi("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
|
||||||
testApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
|
||||||
|
|
||||||
// To use android test orchestrator
|
// JsonDelegate で使う
|
||||||
androidTestUtil("androidx.test:orchestrator:1.4.2")
|
implementation(kotlin("reflect"))
|
||||||
|
// UriSerializer で使う。アカウント設定で状態の保存に kotlinx-serialization-json を使っている
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Vers.kotlinxSerializationLibVersion}")
|
||||||
|
|
||||||
testApi("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
// BitmapUtils で使う
|
||||||
exclude("com.squareup.okio", "okio")
|
implementation("androidx.exifinterface:exifinterface:1.3.7")
|
||||||
exclude("com.squareup.okhttp3", "okhttp")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
|
||||||
}
|
|
||||||
androidTestApi("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
|
||||||
exclude("com.squareup.okio", "okio")
|
|
||||||
exclude("com.squareup.okhttp3", "okhttp")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
|
||||||
}
|
|
||||||
|
|
||||||
// conscrypt をUnitテストするための指定
|
// MovieUtils で使う
|
||||||
// https://github.com/google/conscrypt/issues/649
|
implementation("com.otaliastudios:transcoder:0.10.5")
|
||||||
|
|
||||||
api("org.conscrypt:conscrypt-android:${Vers.conscryptVersion}")
|
// HttpUtils で使う
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}")
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// 単体テスト
|
||||||
|
testImplementation(kotlin("test"))
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// AndroidTest
|
||||||
|
// 紛らわしいのでAndroidTestではkotlin.testを使わない androidTestImplementation(kotlin("test"))
|
||||||
|
androidTestRuntimeOnly("androidx.test:runner:1.5.2")
|
||||||
|
androidTestImplementation("androidx.test:core:${Vers.androidxTestCore}")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:${Vers.androidxTestExtJunit}")
|
||||||
|
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
||||||
|
|
||||||
|
// DispatchersTest で使う
|
||||||
|
androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Vers.androidxLifecycle}")
|
||||||
|
|
||||||
|
// implementation("androidx.core:core-ktx:${Vers.androidxCoreVersion}")
|
||||||
|
//
|
||||||
|
// implementation("androidx.emoji2:emoji2-bundled:${Vers.androidxEmoji2}")
|
||||||
|
// implementation("androidx.emoji2:emoji2-views-helper:${Vers.androidxEmoji2}")
|
||||||
|
// implementation("androidx.emoji2:emoji2-views:${Vers.androidxEmoji2}")
|
||||||
|
// implementation("androidx.emoji2:emoji2:${Vers.androidxEmoji2}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-common-java8:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-process:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-reactivestreams-ktx:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-service:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Vers.lifecycleVersion}")
|
||||||
|
// implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Vers.lifecycleVersion}")
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// implementation("com.astuetz:pagerslidingtabstrip:1.0.1")
|
||||||
|
// implementation("com.caverock:androidsvg-aar:1.4")
|
||||||
|
// implementation("com.github.UnifiedPush:android-connector:2.1.1")
|
||||||
|
//
|
||||||
|
// implementation("com.github.bumptech.glide:annotations:${Vers.glideVersion}")
|
||||||
|
// implementation("com.github.bumptech.glide:glide:${Vers.glideVersion}")
|
||||||
|
// implementation("com.github.hadilq:live-event:1.3.0")
|
||||||
|
//
|
||||||
|
// implementation("com.github.penfeizhou.android.animation:apng:${Vers.apng4AndroidVersion}")
|
||||||
|
//
|
||||||
|
// implementation("com.github.zjupure:webpdecoder:${Vers.webpDecoderVersion}")
|
||||||
|
//
|
||||||
|
// implementation("com.google.android.material:material:${Vers.googleMaterialVersion}")
|
||||||
|
//
|
||||||
|
// implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||||
|
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Vers.kotlinxCoroutinesVersion}")
|
||||||
|
// implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||||
|
// implementation("ru.gildor.coroutines:kotlin-coroutines-okhttp:${Vers.gildorkotlinCoroutinesOkhttp}")
|
||||||
|
|
||||||
|
|
||||||
|
// androidTestImplementation("androidx.test.espresso:espresso-core:${Vers.androidxTestEspressoCoreVersion}")
|
||||||
|
// androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||||
|
// androidTestImplementation("androidx.test.ext:truth:1.5.0")
|
||||||
|
// androidTestImplementation("androidx.test:core-ktx:${Vers.testKtxVersion}")
|
||||||
|
//
|
||||||
|
// testImplementation("androidx.arch.core:core-testing:${Vers.archVersion}")
|
||||||
|
// testImplementation("junit:junit:${Vers.junitVersion}")
|
||||||
|
// testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Vers.kotlinxCoroutinesVersion}")
|
||||||
|
//
|
||||||
|
// // Compose compilerによりkotlinのバージョンを上げられない
|
||||||
|
// //noinspection GradleDependency
|
||||||
|
// testImplementation("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
||||||
|
// //noinspection GradleDependency
|
||||||
|
// androidTestImplementation("org.jetbrains.kotlin:kotlin-test:${Vers.kotlinTestVersion}")
|
||||||
|
//
|
||||||
|
// // To use android test orchestrator
|
||||||
|
// // androidTestUtil("androidx.test:orchestrator:1.4.2")
|
||||||
|
//
|
||||||
|
// testImplementation("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
||||||
|
// exclude("com.squareup.okio", "okio")
|
||||||
|
// exclude("com.squareup.okhttp3", "okhttp")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||||
|
// }
|
||||||
|
// androidTestImplementation("com.squareup.okhttp3:mockwebserver:${Vers.okhttpVersion}") {
|
||||||
|
// exclude("com.squareup.okio", "okio")
|
||||||
|
// exclude("com.squareup.okhttp3", "okhttp")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
|
// exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,55 +4,64 @@ import jp.juggler.crypt.toByteRange
|
||||||
import jp.juggler.util.data.decodeBase64
|
import jp.juggler.util.data.decodeBase64
|
||||||
import jp.juggler.util.data.encodeBase64
|
import jp.juggler.util.data.encodeBase64
|
||||||
import jp.juggler.util.data.encodeBase64Url
|
import jp.juggler.util.data.encodeBase64Url
|
||||||
import org.apache.commons.codec.binary.Base64.encodeBase64String
|
|
||||||
import org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
class ByteRangeTest {
|
class ByteRangeTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ByteRangeや StringUtilsのBase64が、commons-codecの出力結果と一致するか調べる。
|
* ByteRangeや StringUtilsのBase64が、kotlin.io.encoding.Base64 の出力結果と一致するか調べる。
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun testByteRangeBase64() {
|
fun testByteRangeBase64() {
|
||||||
for (len in 0..300) {
|
for (len in 0..300) {
|
||||||
val src = ByteArray(len) { it.toByte() }
|
val src = ByteArray(len) { it.toByte() }
|
||||||
run {
|
run {
|
||||||
val encodedByApacheCodec = encodeBase64URLSafeString(src)
|
|
||||||
|
val kotlinBase64UrlSafe = Base64.UrlSafe
|
||||||
|
|
||||||
|
// kotlin.io の Base64.UrlSafe は 末尾の = パディングを残すので後から除去する必要がある
|
||||||
|
val encodedByKotlinIo = kotlinBase64UrlSafe.encode(src).trimEnd { it=='=' }
|
||||||
|
// ByteRange().encodeBase64Url() はパディングを含まない
|
||||||
val encodeByByteRange = src.toByteRange().encodeBase64Url()
|
val encodeByByteRange = src.toByteRange().encodeBase64Url()
|
||||||
val encodeByUtils = src.encodeBase64Url()
|
// StringUtils の encodeBase64Url() はパディングを含まない
|
||||||
val decodedByUtils = encodeByUtils.decodeBase64()
|
val encodeByStringUtils = src.encodeBase64Url()
|
||||||
|
// もちろんStringUtilsの decodeBase64() でデコードできる
|
||||||
|
val decodedByUtils = encodeByStringUtils.decodeBase64()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"len=$len",
|
"len=$len",
|
||||||
encodedByApacheCodec,
|
encodedByKotlinIo,
|
||||||
encodeByByteRange,
|
encodeByByteRange,
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"len=$len",
|
"len=$len",
|
||||||
encodedByApacheCodec,
|
encodedByKotlinIo,
|
||||||
encodeByUtils,
|
encodeByStringUtils,
|
||||||
)
|
)
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
"len=$len encoded=$encodeByUtils",
|
"len=$len encoded=$encodeByStringUtils",
|
||||||
src,
|
src,
|
||||||
decodedByUtils,
|
decodedByUtils,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
run {
|
run {
|
||||||
val encodedByApacheCodec = encodeBase64String(src)
|
val kotinBase64 = Base64.Default
|
||||||
|
val encodedByKotlinIo = kotinBase64.encode(src)
|
||||||
val encodeByByteRange = src.toByteRange().encodeBase64()
|
val encodeByByteRange = src.toByteRange().encodeBase64()
|
||||||
val encodeByUtils = src.encodeBase64()
|
val encodeByUtils = src.encodeBase64()
|
||||||
val decodedByUtils = encodeByUtils.decodeBase64()
|
val decodedByUtils = encodeByUtils.decodeBase64()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"len=$len",
|
"len=$len",
|
||||||
encodedByApacheCodec,
|
encodedByKotlinIo,
|
||||||
encodeByByteRange,
|
encodeByByteRange,
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"len=$len",
|
"len=$len",
|
||||||
encodedByApacheCodec,
|
encodedByKotlinIo,
|
||||||
encodeByUtils,
|
encodeByUtils,
|
||||||
)
|
)
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
|
|
|
@ -8,7 +8,10 @@ import jp.juggler.util.coroutine.AppDispatchers
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.test.*
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
|
|
|
@ -9,7 +9,6 @@ import android.provider.OpenableColumns
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.annotation.RawRes
|
import androidx.annotation.RawRes
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import okhttp3.internal.closeQuietly
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
private val log = LogCategory("StorageUtils")
|
private val log = LogCategory("StorageUtils")
|
||||||
|
@ -223,7 +222,10 @@ fun getStreamSize(bClose: Boolean, inStream: InputStream): Long {
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
} finally {
|
} finally {
|
||||||
if (bClose) inStream.closeQuietly()
|
if (bClose) try {
|
||||||
|
inStream.close()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* kotlinx-datetime を使った日時関連のユーティリティ
|
* Java8 Time API を使った日時関連のユーティリティ
|
||||||
* - api "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0"
|
* - api "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0"
|
||||||
* - desugar で Java 8 の日時APIを使えるようにする必要がある
|
* - desugar で Java 8 の日時APIを使えるようにする必要がある
|
||||||
* - https://developer.android.com/studio/write/java8-support?hl=ja
|
* - https://developer.android.com/studio/write/java8-support?hl=ja
|
||||||
|
@ -9,9 +9,8 @@
|
||||||
package jp.juggler.util.time
|
package jp.juggler.util.time
|
||||||
|
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import kotlinx.datetime.Instant
|
import java.time.Instant
|
||||||
import kotlinx.datetime.TimeZone
|
import java.time.ZoneId
|
||||||
import kotlinx.datetime.toLocalDateTime
|
|
||||||
|
|
||||||
private val log = LogCategory("TimeUtils")
|
private val log = LogCategory("TimeUtils")
|
||||||
|
|
||||||
|
@ -22,7 +21,7 @@ fun String.parseTimeIso8601() =
|
||||||
when {
|
when {
|
||||||
isBlank() -> null
|
isBlank() -> null
|
||||||
else -> try {
|
else -> try {
|
||||||
Instant.parse(this).toEpochMilliseconds()
|
Instant.parse(this).toEpochMilli()
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.w("parseTime failed. $this")
|
log.w("parseTime failed. $this")
|
||||||
null
|
null
|
||||||
|
@ -33,15 +32,15 @@ fun String.parseTimeIso8601() =
|
||||||
* UI表示用。フォーマットは適当。
|
* UI表示用。フォーマットは適当。
|
||||||
*/
|
*/
|
||||||
fun Long.formatLocalTime(): String {
|
fun Long.formatLocalTime(): String {
|
||||||
val tz = TimeZone.currentSystemDefault()
|
val tz = ZoneId.systemDefault()
|
||||||
val lt = Instant.fromEpochMilliseconds(this).toLocalDateTime(tz)
|
val zdt = Instant.ofEpochMilli(this).atZone(tz)
|
||||||
return "%d/%02d/%02d %02d:%02d:%02d.%03d".format(
|
return "%d/%02d/%02d %02d:%02d:%02d.%03d".format(
|
||||||
lt.year,
|
zdt.year,
|
||||||
lt.monthNumber,
|
zdt.monthValue,
|
||||||
lt.dayOfMonth,
|
zdt.dayOfMonth,
|
||||||
lt.hour,
|
zdt.hour,
|
||||||
lt.minute,
|
zdt.minute,
|
||||||
lt.second,
|
zdt.second,
|
||||||
lt.nanosecond / 1_000_000,
|
zdt.nano / 1_000_000,
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -23,6 +23,7 @@ import android.util.SparseArray
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
@ -351,6 +352,10 @@ fun AppCompatActivity.setNavigationBack(toolbar: Toolbar) =
|
||||||
toolbar.setNavigationOnClickListener {
|
toolbar.setNavigationOnClickListener {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
fun ComponentActivity.setNavigationBack(toolbar: Toolbar) =
|
||||||
|
toolbar.setNavigationOnClickListener {
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
val Float.roundPixels get() = (this + 0.5f).toInt()
|
val Float.roundPixels get() = (this + 0.5f).toInt()
|
||||||
fun DisplayMetrics.dpFloat(src: Float) = (density * src)
|
fun DisplayMetrics.dpFloat(src: Float) = (density * src)
|
||||||
|
|
|
@ -129,7 +129,7 @@ private fun rgbToLab(rgb: Int): Triple<Float, Float, Float> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppCompatActivity.setStatusBarColorCompat(@ColorInt c: Int) {
|
fun Activity.setStatusBarColorCompat(@ColorInt c: Int) {
|
||||||
window?.apply {
|
window?.apply {
|
||||||
statusBarColor = Color.BLACK or c
|
statusBarColor = Color.BLACK or c
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ fun AppCompatActivity.setStatusBarColorCompat(@ColorInt c: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AppCompatActivity.setNavigationBarColorCompat(@ColorInt c: Int) {
|
fun Activity.setNavigationBarColorCompat(@ColorInt c: Int) {
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
// no way to restore to system default, need restart app.
|
// no way to restore to system default, need restart app.
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package jp.juggler.pushreceiverapp
|
package jp.juggler
|
||||||
|
|
||||||
import jp.juggler.util.data.Base128.decodeBase128
|
import jp.juggler.util.data.Base128.decodeBase128
|
||||||
import jp.juggler.util.data.Base128.encodeBase128
|
import jp.juggler.util.data.Base128.encodeBase128
|
||||||
import org.junit.Assert.assertArrayEquals
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
|
||||||
class Base128Test {
|
class Base128Test {
|
||||||
|
|
||||||
|
@ -20,10 +20,10 @@ class Base128Test {
|
||||||
}.toByteArray()
|
}.toByteArray()
|
||||||
val encoded = orig.encodeBase128()
|
val encoded = orig.encodeBase128()
|
||||||
val decoded = encoded.decodeBase128()
|
val decoded = encoded.decodeBase128()
|
||||||
assertArrayEquals(
|
assertContentEquals(
|
||||||
"len=$len,i=$i",
|
|
||||||
orig,
|
orig,
|
||||||
decoded
|
decoded,
|
||||||
|
"len=$len,i=$i",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,11 @@ import jp.juggler.util.data.BinPackList
|
||||||
import jp.juggler.util.data.BinPackMap
|
import jp.juggler.util.data.BinPackMap
|
||||||
import jp.juggler.util.data.decodeBinPack
|
import jp.juggler.util.data.decodeBinPack
|
||||||
import jp.juggler.util.data.encodeBinPack
|
import jp.juggler.util.data.encodeBinPack
|
||||||
import org.junit.Assert.*
|
import kotlin.test.DefaultAsserter.assertEquals
|
||||||
import org.junit.Test
|
import kotlin.test.DefaultAsserter.assertNotNull
|
||||||
|
import kotlin.test.DefaultAsserter.assertTrue
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
|
||||||
class BinPackTest {
|
class BinPackTest {
|
||||||
@Test
|
@Test
|
||||||
|
@ -17,11 +20,12 @@ class BinPackTest {
|
||||||
val decoded = encoded.decodeBinPack()
|
val decoded = encoded.decodeBinPack()
|
||||||
val message = "($v ${v?.javaClass?.simpleName}) dump=${encoded.dump()}"
|
val message = "($v ${v?.javaClass?.simpleName}) dump=${encoded.dump()}"
|
||||||
when {
|
when {
|
||||||
expected is ByteArray -> assertArrayEquals(
|
expected is ByteArray -> assertContentEquals(
|
||||||
"${v?.javaClass?.simpleName} $v",
|
|
||||||
expected,
|
expected,
|
||||||
decoded as? ByteArray
|
decoded as? ByteArray,
|
||||||
|
"${v?.javaClass?.simpleName} $v",
|
||||||
)
|
)
|
||||||
|
|
||||||
v is Set<*> -> {
|
v is Set<*> -> {
|
||||||
val decodedSet = (decoded as? BinPackList)?.toSet()
|
val decodedSet = (decoded as? BinPackList)?.toSet()
|
||||||
assertNotNull("$message decoded?", decodedSet)
|
assertNotNull("$message decoded?", decodedSet)
|
||||||
|
@ -29,6 +33,7 @@ class BinPackTest {
|
||||||
assertTrue("$message containsAll 1", v.containsAll(decodedSet))
|
assertTrue("$message containsAll 1", v.containsAll(decodedSet))
|
||||||
assertTrue("$message containsAll 2", decodedSet.containsAll(v))
|
assertTrue("$message containsAll 2", decodedSet.containsAll(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> assertEquals(
|
else -> assertEquals(
|
||||||
message,
|
message,
|
||||||
expected,
|
expected,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package jp.juggler.base
|
package jp.juggler
|
||||||
|
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
import org.junit.Assert.*
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -5,13 +5,16 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:${Vers.androidGradlePruginVersion}")
|
classpath("com.android.tools.build:gradle:${Vers.androidGradlePrugin}")
|
||||||
|
|
||||||
// room のバージョンの影響で google-services を上げられない場合がある
|
// room のバージョンの影響で google-services を上げられない場合がある
|
||||||
classpath("com.google.gms:google-services:4.4.1")
|
classpath("com.google.gms:google-services:4.4.1")
|
||||||
|
|
||||||
//noinspection DifferentKotlinGradleVersion
|
//noinspection DifferentKotlinGradleVersion
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Vers.kotlinVersion}")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Vers.kotlinVersion}")
|
||||||
|
|
||||||
|
// Compose Compilerの都合でkotlinを上げられない場合がある
|
||||||
|
//noinspection GradleDependency
|
||||||
classpath("org.jetbrains.kotlin:kotlin-serialization:${Vers.kotlinVersion}")
|
classpath("org.jetbrains.kotlin:kotlin-serialization:${Vers.kotlinVersion}")
|
||||||
|
|
||||||
classpath("com.github.bjoernq:unmockplugin:0.7.6")
|
classpath("com.github.bjoernq:unmockplugin:0.7.6")
|
||||||
|
@ -25,6 +28,7 @@ plugins {
|
||||||
kotlin("plugin.serialization") version (Vers.kotlinxSerializationPluginVersion) apply true // !!
|
kotlin("plugin.serialization") version (Vers.kotlinxSerializationPluginVersion) apply true // !!
|
||||||
id("org.jetbrains.kotlin.android") version (Vers.kotlinVersion) apply false
|
id("org.jetbrains.kotlin.android") version (Vers.kotlinVersion) apply false
|
||||||
id("com.google.devtools.ksp") version (Vers.kspVersion) apply false
|
id("com.google.devtools.ksp") version (Vers.kspVersion) apply false
|
||||||
|
// id("com.android.library") version "8.3.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
|
@ -2,54 +2,64 @@ import org.gradle.api.JavaVersion
|
||||||
|
|
||||||
@Suppress("ConstPropertyName")
|
@Suppress("ConstPropertyName")
|
||||||
object Vers {
|
object Vers {
|
||||||
|
|
||||||
const val stBuildToolsVersion = "34.0.0"
|
const val stBuildToolsVersion = "34.0.0"
|
||||||
const val stCompileSdkVersion = 34
|
const val stCompileSdkVersion = 34
|
||||||
const val stTargetSdkVersion = 34
|
const val stTargetSdkVersion = 34
|
||||||
const val stMinSdkVersion = 26
|
const val stMinSdkVersion = 26
|
||||||
|
|
||||||
val javaSourceCompatibility = JavaVersion.VERSION_1_8
|
val javaSourceCompatibility = JavaVersion.VERSION_19
|
||||||
val javaTargetCompatibility = JavaVersion.VERSION_1_8
|
val javaTargetCompatibility = JavaVersion.VERSION_19
|
||||||
|
|
||||||
|
// Compose Compiler 1.5.10 は kotlin 1.9.22 を要求する
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
const val kotlinVersion = "1.9.23"
|
const val kotlinVersion = "1.9.22"
|
||||||
const val kotlinJvmTarget = "1.8"
|
|
||||||
const val kotlinJvmToolchain = 17
|
|
||||||
|
|
||||||
const val androidGradlePruginVersion = "8.3.0"
|
|
||||||
|
|
||||||
const val androidxAnnotationVersion = "1.6.0"
|
|
||||||
const val androidxTestEspressoCoreVersion = "3.5.1"
|
|
||||||
const val androidxTestExtJunitVersion = "1.1.5"
|
|
||||||
const val androidxTestVersion = "1.5.0"
|
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
const val glideVersion = "4.15.1"
|
const val glideVersion = "4.15.1"
|
||||||
|
|
||||||
|
// Compose Compiler 1.5.10 は jvmTarget = "19" を要求する
|
||||||
|
// しかし Android Studio 自体は17で動いてるので単体テスト時に問題がでる
|
||||||
|
const val kotlinJvmTarget = "19"
|
||||||
|
const val kotlinJvmToolchain = 19
|
||||||
|
|
||||||
|
const val androidGradlePrugin = "8.3.0"
|
||||||
|
|
||||||
// const val ankoVersion = "0.10.8"
|
// const val ankoVersion = "0.10.8"
|
||||||
const val appcompatVersion = "1.6.1"
|
// const val commonsCodecVersion = "1.16.0"
|
||||||
const val archVersion = "2.2.0"
|
// const val composeVersion = "1.0.5"
|
||||||
const val benManesVersion = "0.51.0"
|
|
||||||
const val commonsCodecVersion = "1.16.0"
|
const val androidxActivity = "1.8.2"
|
||||||
const val composeVersion = "1.0.5"
|
const val androidxAnnotation = "1.7.1"
|
||||||
|
const val androidxAppcompat = "1.6.1"
|
||||||
|
const val androidxArchCoreTesting = "2.2.0"
|
||||||
|
const val androidxComposeRuntime = "1.6.3"
|
||||||
|
const val androidxComposeUi = "1.6.3"
|
||||||
|
const val androidxCore = "1.12.0"
|
||||||
|
const val androidxEmoji2 = "1.4.0"
|
||||||
|
const val androidxLifecycle = "2.7.0"
|
||||||
|
const val androidxMedia3 = "1.3.0"
|
||||||
|
const val androidxPreferenceKtx = "1.2.1"
|
||||||
|
const val androidxRecyclerView = "1.3.2"
|
||||||
|
const val androidxStartup = "1.1.1"
|
||||||
|
const val androidxTestCore = "1.5.0"
|
||||||
|
const val androidxTestCoreKtx = "1.5.0"
|
||||||
|
const val androidxTestEspressoCore = "3.5.1"
|
||||||
|
const val androidxTestExtJunit = "1.1.5"
|
||||||
|
const val androidxWork = "2.9.0"
|
||||||
|
const val apng4AndroidVersion = "2.25.0"
|
||||||
const val conscryptVersion = "2.5.2"
|
const val conscryptVersion = "2.5.2"
|
||||||
const val coreKtxVersion = "1.12.0"
|
|
||||||
const val desugarLibVersion = "2.0.4"
|
const val desugarLibVersion = "2.0.4"
|
||||||
const val detektVersion = "1.23.4"
|
const val detektVersion = "1.23.4"
|
||||||
const val emoji2Version = "1.4.0"
|
const val gildorkotlinCoroutinesOkhttp = "1.0"
|
||||||
|
const val googleFlexbox="3.0.0"
|
||||||
|
const val googleMaterial = "1.11.0"
|
||||||
const val junitVersion = "4.13.2"
|
const val junitVersion = "4.13.2"
|
||||||
const val koinVersion = "3.5.0"
|
const val koinVersion = "3.5.0"
|
||||||
const val kotlinTestVersion = kotlinVersion
|
|
||||||
const val kotlinxCoroutinesVersion = "1.8.0"
|
const val kotlinxCoroutinesVersion = "1.8.0"
|
||||||
const val kotlinxSerializationPluginVersion = kotlinVersion
|
|
||||||
const val kotlinxSerializationLibVersion = "1.6.3"
|
const val kotlinxSerializationLibVersion = "1.6.3"
|
||||||
const val kspVersion = "$kotlinVersion-1.0.19"
|
const val kotlinxSerializationPluginVersion = kotlinVersion
|
||||||
const val lifecycleVersion = "2.7.0"
|
const val kspVersion = "$kotlinVersion-1.0.17"
|
||||||
const val materialVersion = "1.11.0"
|
const val okhttpVersion = "5.0.0-alpha.12"
|
||||||
const val media3Version = "1.3.0"
|
|
||||||
const val okhttpVersion = "5.0.0-alpha.11"
|
|
||||||
const val preferenceKtxVersion = "1.2.1"
|
|
||||||
const val startupVersion = "1.1.1"
|
|
||||||
const val testKtxVersion = "1.5.0"
|
|
||||||
const val webpDecoderVersion = "2.6.$glideVersion"
|
const val webpDecoderVersion = "2.6.$glideVersion"
|
||||||
const val workVersion = "2.9.0"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,9 @@ android {
|
||||||
lint {
|
lint {
|
||||||
abortOnError = false
|
abortOnError = false
|
||||||
}
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = Vers.javaSourceCompatibility
|
sourceCompatibility = Vers.javaSourceCompatibility
|
||||||
|
@ -52,5 +55,12 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
||||||
|
|
||||||
|
// dismissSafe, systemService, View.gone() など
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
|
|
||||||
|
implementation("androidx.core:core-ktx:${Vers.androidxCore}")
|
||||||
|
implementation("androidx.appcompat:appcompat:${Vers.androidxAppcompat}")
|
||||||
|
implementation("androidx.annotation:annotation:${Vers.androidxAnnotation}")
|
||||||
|
implementation("com.google.android.flexbox:flexbox:${Vers.googleFlexbox}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 JRummy Apps Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import android.graphics.*
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This drawable will draw a simple white and gray chessboard pattern.
|
|
||||||
* It's the pattern you will often see as a background behind a partly transparent image in many applications.
|
|
||||||
*/
|
|
||||||
internal class AlphaPatternDrawable(private val rectangleSize: Int) : Drawable() {
|
|
||||||
private val paint = Paint()
|
|
||||||
private val paintWhite = Paint().apply { color = Color.WHITE }
|
|
||||||
private val paintGray = Paint().apply { color = -0x343434 }
|
|
||||||
private var numRectanglesHorizontal = 0
|
|
||||||
private var numRectanglesVertical = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitmap in which the pattern will be cached.
|
|
||||||
* This is so the pattern will not have to be recreated each time draw() gets called.
|
|
||||||
* Because recreating the pattern i rather expensive. I will only be recreated if the size changes.
|
|
||||||
*/
|
|
||||||
private var bitmap: Bitmap? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on.
|
|
||||||
* We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds
|
|
||||||
*/
|
|
||||||
private fun generatePatternBitmap() {
|
|
||||||
if (bounds.width() <= 0 || bounds.height() <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888)
|
|
||||||
.also { this.bitmap = it }
|
|
||||||
val canvas = Canvas(bitmap)
|
|
||||||
val r = Rect()
|
|
||||||
var verticalStartWhite = true
|
|
||||||
for (i in 0..numRectanglesVertical) {
|
|
||||||
var isWhite = verticalStartWhite
|
|
||||||
for (j in 0..numRectanglesHorizontal) {
|
|
||||||
r.top = i * rectangleSize
|
|
||||||
r.left = j * rectangleSize
|
|
||||||
r.bottom = r.top + rectangleSize
|
|
||||||
r.right = r.left + rectangleSize
|
|
||||||
canvas.drawRect(r, if (isWhite) paintWhite else paintGray)
|
|
||||||
isWhite = !isWhite
|
|
||||||
}
|
|
||||||
verticalStartWhite = !verticalStartWhite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val bitmap = this.bitmap
|
|
||||||
if (bitmap != null && !bitmap.isRecycled) {
|
|
||||||
canvas.drawBitmap(bitmap, null, bounds, paint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOpacity(): Int {
|
|
||||||
return PixelFormat.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
|
||||||
throw UnsupportedOperationException("Alpha is not supported by this drawable.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setColorFilter(cf: ColorFilter?) {
|
|
||||||
throw UnsupportedOperationException("ColorFilter is not supported by this drawable.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBoundsChange(bounds: Rect) {
|
|
||||||
super.onBoundsChange(bounds)
|
|
||||||
val height = bounds.height()
|
|
||||||
val width = bounds.width()
|
|
||||||
numRectanglesHorizontal = ceil((width / rectangleSize.toFloat()).toDouble()).toInt()
|
|
||||||
numRectanglesVertical = ceil((height / rectangleSize.toFloat()).toDouble()).toInt()
|
|
||||||
generatePatternBitmap()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,7 @@ import androidx.core.graphics.ColorUtils
|
||||||
internal class ColorPaletteAdapter(
|
internal class ColorPaletteAdapter(
|
||||||
val colors: IntArray,
|
val colors: IntArray,
|
||||||
var selectedPosition: Int,
|
var selectedPosition: Int,
|
||||||
@ColorShape val colorShape: Int,
|
val colorShape: ColorShape,
|
||||||
val listener: (Int) -> Unit,
|
val listener: (Int) -> Unit,
|
||||||
) : BaseAdapter() {
|
) : BaseAdapter() {
|
||||||
|
|
||||||
|
@ -47,10 +47,9 @@ internal class ColorPaletteAdapter(
|
||||||
private inner class ViewHolder(parent: ViewGroup) {
|
private inner class ViewHolder(parent: ViewGroup) {
|
||||||
val root: View = run {
|
val root: View = run {
|
||||||
LayoutInflater.from(parent.context).inflate(
|
LayoutInflater.from(parent.context).inflate(
|
||||||
if (colorShape == ColorShape.SQUARE) {
|
when (colorShape) {
|
||||||
R.layout.cpv_color_item_square
|
ColorShape.Square -> R.layout.cpv_color_item_square
|
||||||
} else {
|
ColorShape.Circle -> R.layout.cpv_color_item_circle
|
||||||
R.layout.cpv_color_item_circle
|
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
|
@ -81,12 +80,14 @@ internal class ColorPaletteAdapter(
|
||||||
imageView.colorFilter = null
|
imageView.colorFilter = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
alpha <= ColorPickerDialog.ALPHA_THRESHOLD -> {
|
|
||||||
|
alpha <= ALPHA_THRESHOLD -> {
|
||||||
colorPanelView.borderColor = color or -0x1000000
|
colorPanelView.borderColor = color or -0x1000000
|
||||||
imageView.setColorFilter( /*color | 0xFF000000*/Color.BLACK,
|
imageView.setColorFilter( /*color | 0xFF000000*/Color.BLACK,
|
||||||
PorterDuff.Mode.SRC_IN
|
PorterDuff.Mode.SRC_IN
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
colorPanelView.borderColor = originalBorderColor
|
colorPanelView.borderColor = originalBorderColor
|
||||||
imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
||||||
|
|
|
@ -16,7 +16,13 @@
|
||||||
package com.jrummyapps.android.colorpicker
|
package com.jrummyapps.android.colorpicker
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.BitmapShader
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Shader
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -30,6 +36,17 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
|
||||||
|
enum class ColorShape(val attrEnum: Int) {
|
||||||
|
Square(0),
|
||||||
|
Circle(1),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(i: Int): ColorShape =
|
||||||
|
entries.find { it.attrEnum == i } ?: Square
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class draws a panel which which will be filled with a color which can be set. It can be used to show the
|
* This class draws a panel which which will be filled with a color which can be set. It can be used to show the
|
||||||
* currently selected color which you will get from the [ColorPickerView].
|
* currently selected color which you will get from the [ColorPickerView].
|
||||||
|
@ -45,7 +62,7 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The width in pixels of the border surrounding the color panel. */
|
/* The width in pixels of the border surrounding the color panel. */
|
||||||
private val borderWidthPx = DrawingUtils.dpToPx(context, 1f)
|
private val borderWidthPx = context.dpToPx(1f)
|
||||||
|
|
||||||
private val borderPaint = Paint().apply {
|
private val borderPaint = Paint().apply {
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
|
@ -68,7 +85,7 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
private var drawingRect = Rect()
|
private var drawingRect = Rect()
|
||||||
private var colorRect = Rect()
|
private var colorRect = Rect()
|
||||||
|
|
||||||
private var alphaPattern = AlphaPatternDrawable(DrawingUtils.dpToPx(context, 4f))
|
private var alphaPattern = TilePatternDrawable(context.dpToPx(4f))
|
||||||
|
|
||||||
private var showOldColor = false
|
private var showOldColor = false
|
||||||
|
|
||||||
|
@ -88,8 +105,7 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColorShape
|
private var shape = ColorShape.Circle
|
||||||
private var shape = 0
|
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field != value) {
|
if (field != value) {
|
||||||
field = value
|
field = value
|
||||||
|
@ -99,9 +115,9 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPanelView)
|
val a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPanelView)
|
||||||
shape = a.getInt(R.styleable.ColorPanelView_cpv_colorShape, ColorShape.CIRCLE)
|
shape = ColorShape.fromInt(a.getInt(R.styleable.ColorPanelView_cpv_colorShape, -1))
|
||||||
showOldColor = a.getBoolean(R.styleable.ColorPanelView_cpv_showOldColor, false)
|
showOldColor = a.getBoolean(R.styleable.ColorPanelView_cpv_showOldColor, false)
|
||||||
check(!(showOldColor && shape != ColorShape.CIRCLE)) { "Color preview is only available in circle mode" }
|
check(!(showOldColor && shape != ColorShape.Circle)) { "Color preview is only available in circle mode" }
|
||||||
borderColor = a.getColor(R.styleable.ColorPanelView_cpv_borderColor, DEFAULT_BORDER_COLOR)
|
borderColor = a.getColor(R.styleable.ColorPanelView_cpv_borderColor, DEFAULT_BORDER_COLOR)
|
||||||
a.recycle()
|
a.recycle()
|
||||||
|
|
||||||
|
@ -137,13 +153,13 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
borderPaint.color = borderColor
|
borderPaint.color = borderColor
|
||||||
colorPaint.color = color
|
colorPaint.color = color
|
||||||
if (shape == ColorShape.SQUARE) {
|
if (shape == ColorShape.Square) {
|
||||||
if (borderWidthPx > 0) {
|
if (borderWidthPx > 0) {
|
||||||
canvas.drawRect(drawingRect, borderPaint)
|
canvas.drawRect(drawingRect, borderPaint)
|
||||||
}
|
}
|
||||||
alphaPattern.draw(canvas)
|
alphaPattern.draw(canvas)
|
||||||
canvas.drawRect(colorRect, colorPaint)
|
canvas.drawRect(colorRect, colorPaint)
|
||||||
} else if (shape == ColorShape.CIRCLE) {
|
} else if (shape == ColorShape.Circle) {
|
||||||
val outerRadius = measuredWidth / 2
|
val outerRadius = measuredWidth / 2
|
||||||
if (borderWidthPx > 0) {
|
if (borderWidthPx > 0) {
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(
|
||||||
|
@ -176,24 +192,22 @@ class ColorPanelView @JvmOverloads constructor(
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
when (shape) {
|
when (shape) {
|
||||||
ColorShape.SQUARE -> {
|
ColorShape.Square -> {
|
||||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
val height = MeasureSpec.getSize(heightMeasureSpec)
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
setMeasuredDimension(width, height)
|
setMeasuredDimension(width, height)
|
||||||
}
|
}
|
||||||
ColorShape.CIRCLE -> {
|
|
||||||
|
ColorShape.Circle -> {
|
||||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
|
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
|
||||||
setMeasuredDimension(measuredWidth, measuredWidth)
|
setMeasuredDimension(measuredWidth, measuredWidth)
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
if (shape == ColorShape.SQUARE || showOldColor) {
|
if (shape == ColorShape.Square || showOldColor) {
|
||||||
drawingRect.set(
|
drawingRect.set(
|
||||||
paddingLeft,
|
paddingLeft,
|
||||||
paddingTop,
|
paddingTop,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 JRummy Apps Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used for getting the selected color from a color picker dialog.
|
|
||||||
*/
|
|
||||||
interface ColorPickerDialogListener {
|
|
||||||
/**
|
|
||||||
* Callback that is invoked when a color is selected from the color picker dialog.
|
|
||||||
*
|
|
||||||
* @param dialogId
|
|
||||||
* The dialog id used to create the dialog instance.
|
|
||||||
* @param newColor
|
|
||||||
* The selected color
|
|
||||||
*/
|
|
||||||
fun onColorSelected(dialogId: Int, @ColorInt newColor: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback that is invoked when the color picker dialog was dismissed.
|
|
||||||
*
|
|
||||||
* @param dialogId
|
|
||||||
* The dialog id used to create the dialog instance.
|
|
||||||
*/
|
|
||||||
fun onDialogDismissed(dialogId: Int)
|
|
||||||
}
|
|
|
@ -17,8 +17,18 @@ package com.jrummyapps.android.colorpicker
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.*
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.ComposeShader
|
||||||
|
import android.graphics.LinearGradient
|
||||||
|
import android.graphics.Paint
|
||||||
import android.graphics.Paint.Align
|
import android.graphics.Paint.Align
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Shader
|
||||||
import android.graphics.Shader.TileMode
|
import android.graphics.Shader.TileMode
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -57,7 +67,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
private const val BORDER_WIDTH_PX = 1
|
private const val BORDER_WIDTH_PX = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnColorChangedListener {
|
fun interface OnColorChangedListener {
|
||||||
fun onColorChanged(newColor: Int)
|
fun onColorChanged(newColor: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,41 +80,35 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
/**
|
/**
|
||||||
* The width in px of the hue panel.
|
* The width in px of the hue panel.
|
||||||
*/
|
*/
|
||||||
private val huePanelWidthPx =
|
private val huePanelWidthPx = context.dpToPx(HUE_PANEL_WDITH_DP)
|
||||||
DrawingUtils.dpToPx(context, HUE_PANEL_WDITH_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The height in px of the alpha panel
|
* The height in px of the alpha panel
|
||||||
*/
|
*/
|
||||||
private val alphaPanelHeightPx =
|
private val alphaPanelHeightPx = context.dpToPx(ALPHA_PANEL_HEIGH_DP)
|
||||||
DrawingUtils.dpToPx(context, ALPHA_PANEL_HEIGH_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The distance in px between the different
|
* The distance in px between the different
|
||||||
* color panels.
|
* color panels.
|
||||||
*/
|
*/
|
||||||
private val panelSpacingPx =
|
private val panelSpacingPx = context.dpToPx(PANEL_SPACING_DP)
|
||||||
DrawingUtils.dpToPx(context, PANEL_SPACING_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The radius in px of the color palette tracker circle.
|
* The radius in px of the color palette tracker circle.
|
||||||
*/
|
*/
|
||||||
private val circleTrackerRadiusPx =
|
private val circleTrackerRadiusPx = context.dpToPx(CIRCLE_TRACKER_RADIUS_DP)
|
||||||
DrawingUtils.dpToPx(context, CIRCLE_TRACKER_RADIUS_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The px which the tracker of the hue or alpha panel
|
* The px which the tracker of the hue or alpha panel
|
||||||
* will extend outside of its bounds.
|
* will extend outside of its bounds.
|
||||||
*/
|
*/
|
||||||
private val sliderTrackerOffsetPx =
|
private val sliderTrackerOffsetPx = context.dpToPx(SLIDER_TRACKER_OFFSET_DP)
|
||||||
DrawingUtils.dpToPx(context, SLIDER_TRACKER_OFFSET_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Height of slider tracker on hue panel,
|
* Height of slider tracker on hue panel,
|
||||||
* width of slider on alpha panel.
|
* width of slider on alpha panel.
|
||||||
*/
|
*/
|
||||||
private val sliderTrackerSizePx =
|
private val sliderTrackerSizePx = context.dpToPx(SLIDER_TRACKER_SIZE_DP)
|
||||||
DrawingUtils.dpToPx(context, SLIDER_TRACKER_SIZE_DP.toFloat())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the current value of the text that will be shown in the alpha slider.
|
* the current value of the text that will be shown in the alpha slider.
|
||||||
|
@ -147,7 +151,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val satValTrackerPaint = Paint().apply {
|
private val satValTrackerPaint = Paint().apply {
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = DrawingUtils.dpToPx(context, 2f).toFloat()
|
strokeWidth = context.dpToPx(2f).toFloat()
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +159,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
|
|
||||||
private val alphaTextPaint = Paint().apply {
|
private val alphaTextPaint = Paint().apply {
|
||||||
color = -0xe3e3e4
|
color = -0xe3e3e4
|
||||||
textSize = DrawingUtils.dpToPx(context, 14f).toFloat()
|
textSize = context.dpToPx(14f).toFloat()
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
textAlign = Align.CENTER
|
textAlign = Align.CENTER
|
||||||
isFakeBoldText = true
|
isFakeBoldText = true
|
||||||
|
@ -164,7 +168,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
private val hueAlphaTrackerPaint = Paint().apply {
|
private val hueAlphaTrackerPaint = Paint().apply {
|
||||||
color = sliderTrackerColor
|
color = sliderTrackerColor
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = DrawingUtils.dpToPx(context, 2f).toFloat()
|
strokeWidth = context.dpToPx(2f).toFloat()
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +212,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
private var hueRect: Rect? = null
|
private var hueRect: Rect? = null
|
||||||
private var alphaRect: Rect? = null
|
private var alphaRect: Rect? = null
|
||||||
private var startTouchPoint: Point? = null
|
private var startTouchPoint: Point? = null
|
||||||
private var alphaPatternDrawable: AlphaPatternDrawable? = null
|
private var tilePatternDrawable: TilePatternDrawable? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OnColorChangedListener to get notified when the color selected by the user has changed.
|
* OnColorChangedListener to get notified when the color selected by the user has changed.
|
||||||
|
@ -286,6 +290,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
val rect = this.satValRect ?: return
|
val rect = this.satValRect ?: return
|
||||||
val drawingRect = this.drawingRect ?: return
|
val drawingRect = this.drawingRect ?: return
|
||||||
|
|
||||||
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BORDER_WIDTH_PX > 0) {
|
if (BORDER_WIDTH_PX > 0) {
|
||||||
borderPaint.color = borderColor
|
borderPaint.color = borderColor
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
|
@ -366,9 +371,10 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
val p = satValToPoint(sat, bri)
|
val p = satValToPoint(sat, bri)
|
||||||
satValTrackerPaint.color = -0x1000000
|
satValTrackerPaint.color = -0x1000000
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(
|
||||||
p.x.toFloat(), p.y.toFloat(), (circleTrackerRadiusPx - DrawingUtils.dpToPx(
|
p.x.toFloat(),
|
||||||
context, 1f
|
p.y.toFloat(),
|
||||||
)).toFloat(), satValTrackerPaint
|
(circleTrackerRadiusPx - context.dpToPx(1f)).toFloat(),
|
||||||
|
satValTrackerPaint
|
||||||
)
|
)
|
||||||
satValTrackerPaint.color = -0x222223
|
satValTrackerPaint.color = -0x222223
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(
|
||||||
|
@ -381,6 +387,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun drawHuePanel(canvas: Canvas) {
|
private fun drawHuePanel(canvas: Canvas) {
|
||||||
val rect = hueRect
|
val rect = hueRect
|
||||||
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BORDER_WIDTH_PX > 0) {
|
if (BORDER_WIDTH_PX > 0) {
|
||||||
borderPaint.color = borderColor
|
borderPaint.color = borderColor
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
|
@ -440,13 +447,14 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
private fun drawAlphaPanel(canvas: Canvas) {
|
private fun drawAlphaPanel(canvas: Canvas) {
|
||||||
if (!showAlphaPanel) return
|
if (!showAlphaPanel) return
|
||||||
val rect = this.alphaRect ?: return
|
val rect = this.alphaRect ?: return
|
||||||
val alphaPatternDrawable = this.alphaPatternDrawable ?: return
|
val alphaPatternDrawable = this.tilePatternDrawable ?: return
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Will be drawn with hw acceleration, very fast.
|
* Will be drawn with hw acceleration, very fast.
|
||||||
* Also the AlphaPatternDrawable is backed by a bitmap
|
* Also the AlphaPatternDrawable is backed by a bitmap
|
||||||
* generated only once if the size does not change.
|
* generated only once if the size does not change.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("KotlinConstantConditions")
|
||||||
if (BORDER_WIDTH_PX > 0) {
|
if (BORDER_WIDTH_PX > 0) {
|
||||||
borderPaint.color = borderColor
|
borderPaint.color = borderColor
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
|
@ -477,9 +485,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
it,
|
it,
|
||||||
rect.centerX().toFloat(),
|
rect.centerX().toFloat(),
|
||||||
(rect.centerY() + DrawingUtils.dpToPx(
|
(rect.centerY() + context.dpToPx(4f)).toFloat(),
|
||||||
context, 4f
|
|
||||||
)).toFloat(),
|
|
||||||
alphaTextPaint
|
alphaTextPaint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -579,6 +585,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
startTouchPoint = Point(event.x.toInt(), event.y.toInt())
|
startTouchPoint = Point(event.x.toInt(), event.y.toInt())
|
||||||
update = moveTrackersIfNeeded(event)
|
update = moveTrackersIfNeeded(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> update = moveTrackersIfNeeded(event)
|
MotionEvent.ACTION_MOVE -> update = moveTrackersIfNeeded(event)
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
startTouchPoint = null
|
startTouchPoint = null
|
||||||
|
@ -607,16 +614,19 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
hue = pointToHue(event.y)
|
hue = pointToHue(event.y)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
satValRect?.contains(startX, startY) == true -> {
|
satValRect?.contains(startX, startY) == true -> {
|
||||||
val result = pointToSatVal(event.x, event.y)
|
val result = pointToSatVal(event.x, event.y)
|
||||||
sat = result[0]
|
sat = result[0]
|
||||||
bri = result[1]
|
bri = result[1]
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
alphaRect?.contains(startX, startY) == true -> {
|
alphaRect?.contains(startX, startY) == true -> {
|
||||||
alpha = pointToAlpha(event.x.toInt())
|
alpha = pointToAlpha(event.x.toInt())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -679,14 +689,17 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
finalWidth = widthAllowed
|
finalWidth = widthAllowed
|
||||||
finalHeight = heightNeeded
|
finalHeight = heightNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
widthOk -> {
|
widthOk -> {
|
||||||
finalHeight = heightAllowed
|
finalHeight = heightAllowed
|
||||||
finalWidth = widthNeeded
|
finalWidth = widthNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
heightOk -> {
|
heightOk -> {
|
||||||
finalHeight = heightNeeded
|
finalHeight = heightNeeded
|
||||||
finalWidth = widthAllowed
|
finalWidth = widthAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
finalHeight = heightAllowed
|
finalHeight = heightAllowed
|
||||||
finalWidth = widthAllowed
|
finalWidth = widthAllowed
|
||||||
|
@ -769,7 +782,7 @@ class ColorPickerView @JvmOverloads constructor(
|
||||||
val alphaRect = Rect(left, top, right, bottom)
|
val alphaRect = Rect(left, top, right, bottom)
|
||||||
.also { this.alphaRect = it }
|
.also { this.alphaRect = it }
|
||||||
|
|
||||||
alphaPatternDrawable = AlphaPatternDrawable(DrawingUtils.dpToPx(context, 4f))
|
tilePatternDrawable = TilePatternDrawable(context.dpToPx(4f))
|
||||||
.apply {
|
.apply {
|
||||||
setBounds(
|
setBounds(
|
||||||
alphaRect.left,
|
alphaRect.left,
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 JRummy Apps Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The shape of the color preview
|
|
||||||
*/
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@IntDef(ColorShape.SQUARE, ColorShape.CIRCLE)
|
|
||||||
annotation class ColorShape {
|
|
||||||
companion object {
|
|
||||||
const val SQUARE = 0
|
|
||||||
const val CIRCLE = 1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
|
|
||||||
fun parseColorString(src: String): Int {
|
|
||||||
val start = if (src.startsWith("#")) 1 else 0
|
|
||||||
|
|
||||||
fun c1(offset: Int) =
|
|
||||||
src.substring(start + offset, start + offset + 1).toInt(16) * 0x11
|
|
||||||
|
|
||||||
fun c2(offset: Int) =
|
|
||||||
src.substring(start + offset, start + offset + 2).toInt(16)
|
|
||||||
|
|
||||||
return when (src.length - start) {
|
|
||||||
0 -> Color.BLACK
|
|
||||||
1 -> Color.argb(255, c1(0), c1(0), c1(0))
|
|
||||||
2 -> Color.argb(255, c1(0), c1(1), 0x80)
|
|
||||||
3 -> Color.argb(255, c1(0), c1(1), c1(2))
|
|
||||||
4 -> Color.argb(c1(0), c1(1), c1(2), c1(3))
|
|
||||||
5 -> Color.argb(255, c2(0), c2(2), c1(4))
|
|
||||||
6 -> Color.argb(255, c2(0), c2(2), c2(4))
|
|
||||||
7 -> Color.argb(c2(0), c2(2), c2(4), c1(6))
|
|
||||||
8 -> Color.argb(c2(0), c2(2), c2(4), c2(6))
|
|
||||||
else -> Color.WHITE
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Parcelable
|
|
||||||
|
|
||||||
inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String) =
|
|
||||||
if (Build.VERSION.SDK_INT >= 33) {
|
|
||||||
getParcelable(key, T::class.java)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getParcelable(key)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 JRummy Apps Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.jrummyapps.android.colorpicker
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.TypedValue
|
|
||||||
|
|
||||||
internal object DrawingUtils {
|
|
||||||
fun dpToPx(c: Context, dipValue: Float): Int {
|
|
||||||
val metrics = c.resources.displayMetrics
|
|
||||||
val v = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics)
|
|
||||||
val res = (v + 0.5).toInt() // Round
|
|
||||||
// Ensure at least 1 pixel if val was > 0
|
|
||||||
return if (res == 0 && v > 0) 1 else res
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 JRummy Apps Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package com.jrummyapps.android.colorpicker
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This drawable will draw a simple white and gray chessboard pattern.
|
||||||
|
* It's the pattern you will often see as a background behind a partly transparent image in many applications.
|
||||||
|
*/
|
||||||
|
internal class TilePatternDrawable(
|
||||||
|
private val rectangleSize: Int,
|
||||||
|
) : Drawable() {
|
||||||
|
private val rect = Rect()
|
||||||
|
private val paintWhite = Paint().apply { color = Color.WHITE }
|
||||||
|
private val paintGray = Paint().apply { color = -0x343434 }
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
val xEnd = bounds.right
|
||||||
|
val yEnd = bounds.bottom
|
||||||
|
var verticalStartWhite = true
|
||||||
|
for (y in bounds.top until yEnd step rectangleSize) {
|
||||||
|
rect.top = y
|
||||||
|
rect.bottom = min(yEnd, y + rectangleSize)
|
||||||
|
var isWhite = verticalStartWhite
|
||||||
|
for (x in bounds.left until xEnd step rectangleSize) {
|
||||||
|
rect.left = x
|
||||||
|
rect.right = min(xEnd, x + rectangleSize)
|
||||||
|
canvas.drawRect(rect, if (isWhite) paintWhite else paintGray)
|
||||||
|
isWhite = !isWhite
|
||||||
|
}
|
||||||
|
verticalStartWhite = !verticalStartWhite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION")
|
||||||
|
override fun getOpacity() = PixelFormat.OPAQUE
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
throw UnsupportedOperationException("Alpha is not supported by this drawable.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(cf: ColorFilter?) {
|
||||||
|
throw UnsupportedOperationException("ColorFilter is not supported by this drawable.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
{
|
||||||
|
// json5
|
||||||
|
|
||||||
|
// ~/.gradle/caches フォルダ
|
||||||
|
"gradleCacheDir": "/c/Users/tateisu/.gradle/caches",
|
||||||
|
//デバッグ用。指定があればそのフォルダにpomファイルをコピーする。
|
||||||
|
// "pomDumpDir" : null,
|
||||||
|
|
||||||
|
// 不足データの取得に使うリポジトリ
|
||||||
|
"repos": [
|
||||||
|
"https://repo.maven.apache.org/maven2",
|
||||||
|
"https://dl.google.com/android/maven2",
|
||||||
|
"https://www.jitpack.io",
|
||||||
|
],
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// 出力ファイル別の設定
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "fcm",
|
||||||
|
// 出力JSONファイルのpath
|
||||||
|
"outFile": "app/src/fcm/res/raw/dep_list.json",
|
||||||
|
// gradlew dependencies に指定するconfiguration
|
||||||
|
"configuration": "fcmReleaseRuntimeClasspath",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "noFcm",
|
||||||
|
// 出力JSONファイルのpath
|
||||||
|
"outFile": "app/src/noFcm/res/raw/dep_list.json",
|
||||||
|
// gradlew dependencies に指定するconfiguration
|
||||||
|
"configuration": "noFcmReleaseRuntimeClasspath",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Gradleの依存関係からは自動検出できない依存関係
|
||||||
|
"additionalLibs": [
|
||||||
|
{
|
||||||
|
"website": "https://github.com/iamcal/emoji-data",
|
||||||
|
|
||||||
|
"name": "iamcal/emoji-data",
|
||||||
|
"description": "Easy to parse data and spritesheets for emoji",
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"url": "https://opensource.org/license/mit/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"developers": [
|
||||||
|
{
|
||||||
|
"name": "Cal Henderson"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"website": "https://github.com/twitter/twemoji",
|
||||||
|
|
||||||
|
"name": "Twitter Emoji (Twemoji)",
|
||||||
|
"description": "A simple library that provides standard Unicode emoji support across all platforms.",
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"url": "https://opensource.org/license/mit/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"developers": [
|
||||||
|
{
|
||||||
|
"name": "X (fka Twitter)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"website": "https://github.com/jrummyapps/colorpicker",
|
||||||
|
|
||||||
|
"name": "Color Picker",
|
||||||
|
"description": "Yet another open source color picker for Android.",
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"url": "https://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"developers": [
|
||||||
|
{
|
||||||
|
"name": "Jared Rummler"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"website": "https://github.com/Kotlin/anko",
|
||||||
|
|
||||||
|
"name": "Kotlin/anko (Anko Layouts)",
|
||||||
|
"description": "a fast and type-safe way to write dynamic Android layouts",
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"url": "https://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// 既知のライセンス情報
|
||||||
|
// - URLの微妙な差異を吸収するため、あらかじめよくあるライセンスを列挙しておく
|
||||||
|
// - 実行時にPOMから取得した情報でデータが追加される
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"name": "The Apache Software License, Version 2.0",
|
||||||
|
"shortName": "Apache-2.0",
|
||||||
|
"urls": [
|
||||||
|
"https://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
"https://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||||
|
"http://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
"http://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||||
|
"https://api.github.com/licenses/apache-2.0",
|
||||||
|
"https://github.com/elye/loaderviewlibrary/blob/master/LICENSE",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MIT License",
|
||||||
|
"shortName": "MIT",
|
||||||
|
"urls": [
|
||||||
|
"https://opensource.org/license/mit/",
|
||||||
|
"https://opensource.org/licenses/MIT",
|
||||||
|
"https://github.com/lisawray/groupie/blob/master/LICENSE.md",
|
||||||
|
"https://github.com/omadahealth/SwipyRefreshLayoutblob/master/LICENSE",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "The 2-Clause BSD License",
|
||||||
|
"shortName": "BSD-2-Clause",
|
||||||
|
"urls": [
|
||||||
|
"https://opensource.org/license/bsd-2-clause/",
|
||||||
|
"http://www.opensource.org/licenses/bsd-license",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"name": "SQLCipher Community Edition License",
|
||||||
|
"shortName": "SQLCipher Community Edition License",
|
||||||
|
"urls": [
|
||||||
|
"https://www.zetetic.net/sqlcipher/license/",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"name": "Amazon Software License",
|
||||||
|
"shortName": "Amazon Software License",
|
||||||
|
"urls": [
|
||||||
|
"https://aws.amazon.com/asl/",
|
||||||
|
"http://aws.amazon.com/asl/",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unicode, Inc. License",
|
||||||
|
"shortName": "Unicode License",
|
||||||
|
"urls": [
|
||||||
|
"https://www.unicode.org/copyright.html#License",
|
||||||
|
"http://www.unicode.org/copyright.html#License",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 以下のライブラリはpomにDevelopers指定がなくても許容する
|
||||||
|
"libsMissingDevelopers": [
|
||||||
|
"androidx.databinding:databinding-",
|
||||||
|
"androidx.databinding:databinding-adapters",
|
||||||
|
"androidx.databinding:viewbinding",
|
||||||
|
"com.amazonaws:aws-android-sdk-",
|
||||||
|
"com.github.alexzhirkevich:custom-qr-generator",
|
||||||
|
"com.github.penfeizhou.android.animation",
|
||||||
|
"com.google.android.datatransport:transport-",
|
||||||
|
"com.google.android.gms:play-services-",
|
||||||
|
"com.google.code.findbugs:jsr305:",
|
||||||
|
"com.google.code.gson:gson:",
|
||||||
|
"com.google.errorprone:error_prone_annotations:",
|
||||||
|
"com.google.firebase:firebase-",
|
||||||
|
"com.google.guava:failureaccess",
|
||||||
|
"com.google.guava:guava",
|
||||||
|
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
|
||||||
|
"com.google.zxing:core",
|
||||||
|
"com.jakewharton.picasso:picasso2-okhttp3-downloader",
|
||||||
|
"com.squareup.picasso:picasso",
|
||||||
|
"com.theartofdev.edmodo:android-image-cropper",
|
||||||
|
"io.realm:android-adapters",
|
||||||
|
"javax.inject:javax.inject",
|
||||||
|
"org.apache.httpcomponents:httpclient",
|
||||||
|
"org.apache.httpcomponents:httpcore",
|
||||||
|
"org.apache.httpcomponents:httpmime",
|
||||||
|
"org.eclipse.paho:org.eclipse.paho.client.mqttv3",
|
||||||
|
],
|
||||||
|
// 以下のライブラリはpomにライセンス指定がなくても許容する
|
||||||
|
"libsMissingLicenses": [
|
||||||
|
"com.github.alexzhirkevich:custom-qr-generator",
|
||||||
|
"com.github.penfeizhou.android.animation",
|
||||||
|
"com.google.guava:failureaccess",
|
||||||
|
"com.google.guava:guava",
|
||||||
|
"com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava",
|
||||||
|
"com.google.zxing:core",
|
||||||
|
"com.squareup.picasso:picasso",
|
||||||
|
"com.theartofdev.edmodo:android-image-cropper",
|
||||||
|
"commons-codec:commons-codec",
|
||||||
|
"commons-logging:commons-logging",
|
||||||
|
"org.apache.httpcomponents:httpclient",
|
||||||
|
"org.apache.httpcomponents:httpcore",
|
||||||
|
"org.apache.httpcomponents:httpmime",
|
||||||
|
"org.eclipse.paho:org.eclipse.paho.client.mqttv3",
|
||||||
|
],
|
||||||
|
// 以下のライブラリはpomにライセンス名の指定がなくても許容する
|
||||||
|
"libsMissingLicenseName": [
|
||||||
|
],
|
||||||
|
// 以下のライブラリはpomにWebサイト指定がなくても許容する
|
||||||
|
"libsMissingWebSite": [
|
||||||
|
"androidx.databinding:viewbinding",
|
||||||
|
"com.github.alexzhirkevich:custom-qr-generator",
|
||||||
|
"com.github.penfeizhou.android.animation",
|
||||||
|
"com.google.android.datatransport:transport-",
|
||||||
|
"com.google.android.gms:play-services-", // 前方一致
|
||||||
|
"com.google.errorprone:error_prone_annotations",
|
||||||
|
"com.google.firebase:firebase-", // 前方一致
|
||||||
|
"com.google.guava:failureaccess",
|
||||||
|
"com.google.guava:listenablefuture",
|
||||||
|
"com.google.zxing:core",
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1,418 @@
|
||||||
|
#!/usr/bin/perl --
|
||||||
|
|
||||||
|
# - カレントディレクトリで./gradlew :app:dependencies して依存関係を列挙する
|
||||||
|
# - ユーザフォルダの.gradle/ にあるpomファイルを探索する
|
||||||
|
# - 依存関係とpomファイルを突き合わせて json を出力する
|
||||||
|
|
||||||
|
use 5.32.1;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Getopt::Long;
|
||||||
|
use File::Basename;
|
||||||
|
use File::Find;
|
||||||
|
use File::Path qw(make_path remove_tree);
|
||||||
|
use File::Copy;
|
||||||
|
use JSON5;
|
||||||
|
use JSON::XS;
|
||||||
|
use Types::Serialiser;
|
||||||
|
use constant{
|
||||||
|
true =>Types::Serialiser::true,
|
||||||
|
false =>Types::Serialiser::false,
|
||||||
|
};
|
||||||
|
use XML::XPath;
|
||||||
|
use XML::XPath::XMLParser;
|
||||||
|
use Data::Dump qw(dump);
|
||||||
|
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
|
||||||
|
use LWP::UserAgent;
|
||||||
|
my $ua = LWP::UserAgent->new(timeout => 10);
|
||||||
|
$ua->env_proxy;
|
||||||
|
|
||||||
|
sub loadFile($){
|
||||||
|
my($file)=@_;
|
||||||
|
open(my $fh,"<:raw",$file) or die "$! $file";
|
||||||
|
local $/ = undef;
|
||||||
|
my $data = <$fh>;
|
||||||
|
close($fh) or die "$! $file";
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 出力フォルダがなければ作る
|
||||||
|
sub prepareDirectory($){
|
||||||
|
my($dir)=@_;
|
||||||
|
return if -d $dir;
|
||||||
|
make_path($dir) or die "can't create directory. $dir";
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################
|
||||||
|
# オプション解析、値の検証、出力フォルダの作成
|
||||||
|
|
||||||
|
# 設定ファイル
|
||||||
|
my $configFile = "config/dependencyJsonConfig.json5";
|
||||||
|
GetOptions ("configFile=s" => \$configFile) or die("bad options.\n");
|
||||||
|
my $config = decode_json5(loadFile $configFile);
|
||||||
|
|
||||||
|
# ライブラリのライセンス情報
|
||||||
|
my $initialLicenses = $config->{licenses}
|
||||||
|
or die "config.initialLicenses is missing.";
|
||||||
|
|
||||||
|
# POM解析の検証用データ
|
||||||
|
# - POMのXML解析時に取得漏れがあればエラーとしたい
|
||||||
|
# - しかしXMLに元々情報がない場合はエラーを出したくない
|
||||||
|
# - なので情報がないライブラリを列挙しておく
|
||||||
|
|
||||||
|
# 以下のライブラリはpomにDevelopersがなくても許容する
|
||||||
|
my $libsMissingDevelopers = $config->{libsMissingDevelopers}
|
||||||
|
or die"config.libsMissingDevelopers is missing.";
|
||||||
|
|
||||||
|
# 以下のライブラリはpomにライセンス指定がなくても許容する
|
||||||
|
my $libsMissingLicenses = $config->{libsMissingLicenses}
|
||||||
|
or die"config.libsMissingLicenses is missing.";
|
||||||
|
|
||||||
|
# 以下のライブラリはpomにライセンス名の指定がなくても許容する
|
||||||
|
my $libsMissingLicenseName = $config->{libsMissingLicenseName}
|
||||||
|
or die"config.libsMissingLicenseName is missing.";
|
||||||
|
|
||||||
|
# 以下のライブラリはpomにWebサイトがなくても許容する
|
||||||
|
my $libsMissingWebSite = $config->{libsMissingWebSite}
|
||||||
|
or die"config.libsMissingWebSite is missing.";
|
||||||
|
|
||||||
|
# idがprefixesリストのいずれかに前方一致するなら真
|
||||||
|
sub matchLibs($$){
|
||||||
|
my($id,$prefixes)=@_;
|
||||||
|
for my $prefix(@$prefixes){
|
||||||
|
return true if $id =~/\A$prefix/;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
# デバッグ用。指定があればそのフォルダにpomファイルをコピーする。
|
||||||
|
my $pomDir = $config->{pomDumpDir};
|
||||||
|
$pomDir and prepareDirectory( $pomDir );
|
||||||
|
|
||||||
|
# pomのメタ情報を読む
|
||||||
|
sub readPomInfo($$){
|
||||||
|
my($name, $xp)=@_;
|
||||||
|
my $groupId = $xp->findvalue('/project/groupId')->value()
|
||||||
|
|| $xp->findvalue('/project/parent/groupId')->value()
|
||||||
|
|| die "missing groupId in $name";
|
||||||
|
|
||||||
|
my $artifactId = $xp->findvalue('/project/artifactId')->value()
|
||||||
|
|| $xp->findvalue('/project/parent/artifactId')->value()
|
||||||
|
|| die "missing artifactId in $name";
|
||||||
|
|
||||||
|
my $version = $xp->findvalue('/project/version')->value()
|
||||||
|
|| $xp->findvalue('/project/parent/version')->value()
|
||||||
|
|| die "missing version in $name";
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupId => $groupId,
|
||||||
|
artifactId => $artifactId,
|
||||||
|
version => $version,
|
||||||
|
#
|
||||||
|
fullName => "$groupId:$artifactId:$version",
|
||||||
|
groupAndArtifact => "$groupId:$artifactId",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# pomを読んで出力用データに変換する
|
||||||
|
sub parsePom($$){
|
||||||
|
my($errors,$found) = @_;
|
||||||
|
my $pomInfo = $found->{pomInfo};
|
||||||
|
my $id = $found->{dep};
|
||||||
|
|
||||||
|
# デバッグ用:pomファイルをコピーする
|
||||||
|
# スクリプトから使う訳ではない
|
||||||
|
if($pomDir){
|
||||||
|
# idの:を_に変更する
|
||||||
|
my $idSafe = $id;
|
||||||
|
$idSafe =~ s/:/_/g;
|
||||||
|
# ファイルがまだなければコピーする
|
||||||
|
my $outPomFile = "$pomDir/$idSafe.pom";
|
||||||
|
-e $outPomFile or copy($pomInfo->{pomFile}, $outPomFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $info = {
|
||||||
|
id => $id,
|
||||||
|
artifactVersion => $pomInfo->{version},
|
||||||
|
};
|
||||||
|
|
||||||
|
# xpathを使ってXMLからデータを読む
|
||||||
|
my $xp = XML::XPath->new(filename => $pomInfo->{pomFile});
|
||||||
|
|
||||||
|
my $developers = $info->{developers} = [];
|
||||||
|
for my $node( $xp->findnodes("/project/developers/developer") ){
|
||||||
|
my $name = $node->findvalue("name")->value()
|
||||||
|
|| $node->findvalue("id")->value();
|
||||||
|
if(not $name){
|
||||||
|
push @$errors,"[$id]missing developer.name";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
push @$developers,{ name => $name, };
|
||||||
|
}
|
||||||
|
|
||||||
|
if( not @$developers
|
||||||
|
and not matchLibs($id,$libsMissingDevelopers)
|
||||||
|
){
|
||||||
|
push @$errors,"[$id]missing developers.";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $licenses = $info->{licenses} = [];
|
||||||
|
for my $node( $xp->findnodes("/project/licenses/license") ){
|
||||||
|
my $url = $node->findvalue('url')->value();
|
||||||
|
if(not $url){
|
||||||
|
push @$errors,"[$id]missing license.url";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $name = $node->findvalue('name')->value();
|
||||||
|
if( not $name){
|
||||||
|
if( matchLibs($id,$libsMissingLicenseName) ){
|
||||||
|
$name = "Unknown license";
|
||||||
|
}else{
|
||||||
|
push @$errors,"[$id]missing license.name";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push @$licenses, { name => $name, url => $url, };
|
||||||
|
}
|
||||||
|
|
||||||
|
if( not @$licenses
|
||||||
|
and not matchLibs($id,$libsMissingLicenses)
|
||||||
|
){
|
||||||
|
push @$errors,"[$id]missing licenses.";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $name = $xp->findvalue('/project/name')->value();
|
||||||
|
$name and $info->{name} = $name;
|
||||||
|
|
||||||
|
my $description = $xp->findvalue('/project/description')->value();
|
||||||
|
if($description){
|
||||||
|
$description =~ s/\A\s+//;
|
||||||
|
$description =~ s/\s+\z//;
|
||||||
|
$description and $info->{description} = $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $webSite = $info->{website} = $xp->findvalue('/project/url')->value()
|
||||||
|
|| $xp->findvalue('/project/scm/url')->value();
|
||||||
|
|
||||||
|
if($webSite){
|
||||||
|
$info->{website} = $webSite;
|
||||||
|
}elsif( not matchLibs($id,$libsMissingWebSite) ){
|
||||||
|
push @$errors,"[$id]missing website.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# aarファイルにはpom.xmlが含まれないのでmvnコマンドで取得する。
|
||||||
|
sub downloadPom($){
|
||||||
|
my($dep)=@_;
|
||||||
|
# ダウンロードしたjarの保存フォルダ
|
||||||
|
my $dirDlSave = ".depCheck/download";
|
||||||
|
prepareDirectory( $dirDlSave );
|
||||||
|
# ダウンロードしたファイル
|
||||||
|
my $file = "$dirDlSave/$dep.pom";
|
||||||
|
$file =~ s/:/_/g;
|
||||||
|
if( not -f $file){
|
||||||
|
say "downloading pom for $dep";
|
||||||
|
$dep =~ m|^([^:]+):([^:]+):([^:]+)$|;
|
||||||
|
my($groupId,$artifactId,$version)=($1,$2,$3);
|
||||||
|
my $groupIdSlashed = $groupId;
|
||||||
|
$groupIdSlashed =~ s|\.|/|g;
|
||||||
|
|
||||||
|
my $successResponse;
|
||||||
|
my @errorResponses;
|
||||||
|
for my $repo(@{$config->{repos}}){
|
||||||
|
my $url = "$repo/$groupIdSlashed/$artifactId/$version/$artifactId-$version.pom";
|
||||||
|
my $response = $ua->get($url);
|
||||||
|
if( $response->is_success) {
|
||||||
|
$successResponse = $response;
|
||||||
|
last;
|
||||||
|
}else{
|
||||||
|
push @errorResponses,$response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$successResponse){
|
||||||
|
for(@errorResponses){
|
||||||
|
say $_->status_line ," ", $_->request->uri;
|
||||||
|
}
|
||||||
|
die "can't download $dep.";
|
||||||
|
}
|
||||||
|
open(my $fh,">:raw",$file) or die "$! $file";
|
||||||
|
print $fh $successResponse->content;
|
||||||
|
close($fh) or die "$! $file";
|
||||||
|
}
|
||||||
|
my $xp = XML::XPath->new(filename => $file);
|
||||||
|
my $pomInfo = readPomInfo($file,$xp);
|
||||||
|
$pomInfo->{pomFile} = $file;
|
||||||
|
return $pomInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
# gradleで依存関係を列挙する
|
||||||
|
sub listingDependencies($){
|
||||||
|
my($configuration)=@_;
|
||||||
|
|
||||||
|
my $cmd = "./gradlew -q --no-configuration-cache :app:dependencies --configuration $configuration";
|
||||||
|
say $cmd;
|
||||||
|
open(my $fh,"-|",$cmd) or die "failed to get dependencies: $!";
|
||||||
|
|
||||||
|
my %deps;
|
||||||
|
while(<$fh>){
|
||||||
|
s/[\x0d\x0a]+//;
|
||||||
|
s/\s+\z//;
|
||||||
|
|
||||||
|
# 依存関係は5文字単位でインデントされる
|
||||||
|
next if not s/\A[ \\|+-]{5,}//;
|
||||||
|
|
||||||
|
# 子プロジェクトは対象外
|
||||||
|
next if /^project :/;
|
||||||
|
|
||||||
|
# 末尾の注釈を除去
|
||||||
|
s/\s*\([c*]\)$//;
|
||||||
|
|
||||||
|
# "->" の対応:バージョンのみが変わる場合
|
||||||
|
s/([^ :]+?) -> ([^ :]+?)$/$2/;
|
||||||
|
# "->" の対応:パッケージごと変わる場合
|
||||||
|
s/(\S+?) -> (\S+?)$/$2/;
|
||||||
|
|
||||||
|
$_ and $deps{$_} = 1;
|
||||||
|
}
|
||||||
|
close($fh) or die "failed to get dependencies: $!";
|
||||||
|
|
||||||
|
my $depsCount = 0+(keys %deps);
|
||||||
|
$depsCount or die "ERROR: dependencies not found!";
|
||||||
|
say "$depsCount dependencies found.";
|
||||||
|
|
||||||
|
return \%deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 依存関係とpomを照合してライブラリ毎の出力データを読み取る
|
||||||
|
sub mergeDepsAndPoms($){
|
||||||
|
my($depMap)=@_;
|
||||||
|
|
||||||
|
# 依存関係とpomを照合して @founds と @missings に分類する
|
||||||
|
my @founds;
|
||||||
|
for my $dep (sort keys %$depMap){
|
||||||
|
my $pomInfo = downloadPom($dep);
|
||||||
|
push @founds, {
|
||||||
|
dep => $dep,
|
||||||
|
pomInfo=>$pomInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# pomのパース
|
||||||
|
my @errors;
|
||||||
|
my @info = map{ parsePom(\@errors, $_) } @founds;
|
||||||
|
if(@errors){
|
||||||
|
say $_ for @errors;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
my $size = 0 + @info;
|
||||||
|
say "$size library information parsed.";
|
||||||
|
|
||||||
|
return \@info;
|
||||||
|
}
|
||||||
|
|
||||||
|
# @$licenses の要素でURLがマッチするものを返す
|
||||||
|
sub findLisenceByUrl($$){
|
||||||
|
my($licenses,$url) = @_;
|
||||||
|
for( @$licenses){
|
||||||
|
return $_ if grep{ $_ eq $url } @{$_->{urls}};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ライセンスのshortNameを返す
|
||||||
|
# @$licensesにデータがなければ追加する
|
||||||
|
sub licenseShortName($$){
|
||||||
|
my($licenses,$json)=@_;
|
||||||
|
|
||||||
|
my($item) = findLisenceByUrl($licenses,$json->{url});
|
||||||
|
if(not $item){
|
||||||
|
$item = {
|
||||||
|
shortName => $json->{name},
|
||||||
|
name => $json->{name},
|
||||||
|
urls =>[ $json->{url} ],
|
||||||
|
};
|
||||||
|
push @$licenses,$item;
|
||||||
|
}
|
||||||
|
return $item->{shortName};
|
||||||
|
}
|
||||||
|
|
||||||
|
# ライセンス情報をまとめる
|
||||||
|
sub compactLisences($$){
|
||||||
|
my($initialLicenseList,$libs)=@_;
|
||||||
|
|
||||||
|
# 変更するライセンスリスト
|
||||||
|
# ディープコピーする
|
||||||
|
my $licenses = decode_json encode_json $initialLicenseList;
|
||||||
|
|
||||||
|
# ライブラリごとにライセンスのリストがあるので、それをshortNameのリストに変換する
|
||||||
|
for my $lib (@$libs){
|
||||||
|
@{$lib->{licenses}} = map{ licenseShortName($licenses,$_) } @{$lib->{licenses}};
|
||||||
|
}
|
||||||
|
|
||||||
|
# 出力結果の並び順を安定させるため、ライセンス一覧をshortNameでソートする
|
||||||
|
@$licenses = sort {$a->{shortName} cmp $b->{shortName} } @$licenses;
|
||||||
|
|
||||||
|
say "licenses:";
|
||||||
|
for(@$licenses){
|
||||||
|
my $url = $_->{urls}[0];
|
||||||
|
say " [$_->{shortName}] name='$_->{name}' url=$url";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $licenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 情報をJSONファイルに出力
|
||||||
|
sub outputDepJson($$$){
|
||||||
|
my($outFile,$libs,$licences)=@_;
|
||||||
|
open(my $fh,">:raw",$outFile) or die "$outFile $!";
|
||||||
|
print $fh encode_json {
|
||||||
|
libs => $libs,
|
||||||
|
licenses => $licences,
|
||||||
|
};
|
||||||
|
close($fh) or die "$outFile $!";
|
||||||
|
}
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# - 出力ファイルごとの処理
|
||||||
|
# - ただしGradleキャッシュのスキャンは1回だけ
|
||||||
|
|
||||||
|
my $outputs = $config->{outputs} or die "contif.outputs is missing.";
|
||||||
|
@$outputs or die "contif.outputs is empty.";
|
||||||
|
|
||||||
|
# validation
|
||||||
|
my $outIndex = 0;
|
||||||
|
for my $out (@$outputs){
|
||||||
|
my $name = $out->{name} or die "config.outputs[$outIndex].name is missing.";
|
||||||
|
$out->{outFile} or die "config.outputs[$name].outFile is missing.";
|
||||||
|
$out->{configuration} or die "config.outputs[$name].configuration is missing.";
|
||||||
|
prepareDirectory( dirname($out->{outFile}) );
|
||||||
|
|
||||||
|
# gradleで依存関係を列挙する
|
||||||
|
say "# [$name] listing dependencies ...";
|
||||||
|
$out->{deps} = listingDependencies $out->{configuration};
|
||||||
|
|
||||||
|
# 依存関係とpomを照合してライブラリ毎の出力データを読み取る
|
||||||
|
say "# [$name] read lib data from dependencies and pom data.";
|
||||||
|
my $libs = mergeDepsAndPoms($out->{deps});
|
||||||
|
|
||||||
|
# 追加の依存関係
|
||||||
|
my $addItems = decode_json encode_json $config->{additionalLibs};
|
||||||
|
@$libs = ( @$addItems , @$libs );
|
||||||
|
|
||||||
|
# ライセンス情報をまとめる
|
||||||
|
say "# [$name] compacting licenses ...";
|
||||||
|
my $licenses = compactLisences($initialLicenses,$libs);
|
||||||
|
|
||||||
|
# 情報をJSONファイルに出力
|
||||||
|
say "# [$name] save to json $out->{outFile}";
|
||||||
|
outputDepJson($out->{outFile},$libs,$licenses);
|
||||||
|
|
||||||
|
++$outIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
say "complete!!";
|
|
@ -68,4 +68,8 @@ dependencies {
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
implementation(project(":apng_android"))
|
implementation(project(":apng_android"))
|
||||||
|
implementation("androidx.appcompat:appcompat:${Vers.androidxAppcompat}")
|
||||||
|
|
||||||
|
// ないとなぜかIDE上にエラーが出る
|
||||||
|
implementation("androidx.activity:activity-ktx:${Vers.androidxActivity}")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue