targetSdkVersion 31

This commit is contained in:
tateisu 2021-10-28 05:58:19 +09:00
parent d017a47363
commit 9c68857e6e
42 changed files with 2504 additions and 2404 deletions

View File

@ -3,6 +3,11 @@
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8">
<module name="apng" target="1.7" />
<module name="SubwayTooter.apng_android" target="11" />
<module name="SubwayTooter.app" target="11" />
<module name="SubwayTooter.colorpicker" target="11" />
<module name="SubwayTooter.emoji" target="11" />
<module name="SubwayTooter.sample_apng" target="11" />
</bytecodeTargetLevel>
</component>
</project>

View File

@ -4,10 +4,10 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -2,8 +2,10 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintButtonStyle" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AndroidLintPluralsCandidate" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AndroidLintSetTextI18n" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="BlockingMethodInNonBlockingContext" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ClassName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ConstantConditionIf" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ConvertTwoComparisonsToRangeCheck" enabled="false" level="INFO" enabled_by_default="false" />
@ -23,14 +25,27 @@
<option name="m_maxLength" value="32" />
</inspection_tool>
<inspection_tool class="JoinDeclarationAndAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JsonStandardCompliance" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="LocalVariableName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LoopToCallChain" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="NumericOverflow" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ObjectPropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessaryModuleDependencyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UseWithIndex" enabled="false" level="INFO" enabled_by_default="false" />
<inspection_tool class="XmlDefaultAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XmlDuplicatedId" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="XmlHighlighting" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="XmlUnusedNamespaceDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="YAMLDuplicatedKeys" enabled="false" level="ERROR" enabled_by_default="false" />
</profile>
</component>

View File

@ -11,17 +11,17 @@ repositories {
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'src/lib')
implementation fileTree(include: ['*.jar'], dir: 'src/lib')
implementation "com.google.guava:guava:28.1-jre"
implementation "org.jetbrains.kotlin:kotlin-stdlib"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
def ktor_version="1.5.0"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-cio:$ktor_version"
implementation "io.ktor:ktor-client-features:$ktor_version"
implementation "io.ktor:ktor-client-encoding:$ktor_version"
def ktorVersion="1.5.0"
implementation "io.ktor:ktor-client-core:$ktorVersion"
implementation "io.ktor:ktor-client-cio:$ktorVersion"
implementation "io.ktor:ktor-client-features:$ktorVersion"
implementation "io.ktor:ktor-client-encoding:$ktorVersion"
// StringEscapeUtils.unescapeHtml4
implementation "org.apache.commons:commons-text:1.9"
@ -32,4 +32,8 @@ dependencies {
test {
useJUnitPlatform()
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -30,11 +30,22 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
freeCompilerArgs += [
"-Xopt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xopt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-Xopt-in=androidx.compose.animation.ExperimentalAnimationApi",
]
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
buildTypes {
release {
@ -57,13 +68,6 @@ android {
}
}
dexOptions {
jumboMode = true
preDexLibraries true
maxProcessCount 10
javaMaxHeapSize "3g"
}
// Generate Signed APK
android.applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
@ -73,7 +77,8 @@ android {
def versionName = defaultConfig.versionName
def flavor = variant.flavorName
def date = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date())
outputFileName = "../../SubwayTooter-${flavor}-${versionCode}-${versionName}-${date}.apk"
def branch = gitBranch()
outputFileName = "../../SubwayTooter-${branch}-${flavor}-${versionCode}-${versionName}-${date}.apk"
}
}
}
@ -81,12 +86,27 @@ android {
packagingOptions {
// https://github.com/Kotlin/kotlinx.coroutines/issues/1064
pickFirst("META-INF/atomicfu.kotlin_module")
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
useLibrary 'android.test.base'
useLibrary 'android.test.mock'
}
static def gitBranch() {
def branch = "(no branch)"
def proc = "git status".execute()
proc.in.eachLine { line ->
def matcher = line =~ /\AOn branch (\S+)/
if (matcher) branch = matcher.group(1)
}
proc.err.eachLine { line -> println line }
proc.waitFor()
branch
}
kapt {
useBuildCache = true
}
@ -109,20 +129,17 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
//noinspection KtxExtensionAvailable
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// DrawerLayout
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
// NavigationView
implementation "com.google.android.material:material:1.3.0"
implementation "com.google.android.material:material:1.4.0"
// PreferenceManager
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.exifinterface:exifinterface:1.3.2"
implementation "androidx.exifinterface:exifinterface:1.3.3"
// CustomTabs
implementation "androidx.browser:browser:1.3.0"
@ -155,19 +172,17 @@ dependencies {
testImplementation "junit:junit:$junit_version" // kotlin-testとjunitを併用
implementation 'com.squareup.okhttp3:okhttp:4.8.1'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.8.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1'
def okhttpVersion = "4.9.2"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
androidTestImplementation "'com.squareup.okhttp3:mockwebserver:$okhttpVersion"
def glideVersion = '4.11.0'
def glideVersion = '4.12.0'
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation "com.github.bumptech.glide:annotations:$glideVersion"
implementation( "com.github.bumptech.glide:okhttp3-integration:$glideVersion"){
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion") {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
//
// glide 4.9.0 okhttp3 3.9.1使
// http://bumptech.github.io/glide/int/about.html#how-do-i-use-a-specific-version-of-okhttp-volley-or-other-third-party-library
}
kapt "com.github.bumptech.glide:compiler:$glideVersion"
@ -190,7 +205,7 @@ dependencies {
implementation 'com.astuetz:pagerslidingtabstrip:1.0.1'
implementation 'com.google.android.exoplayer:exoplayer:2.15.0'
implementation 'com.google.android.exoplayer:exoplayer:2.15.1'
/*
WARNING: [Processor] Library '…\exoplayer-ui-2.12.0.aar' contains references to both AndroidX and old support library. This seems like the library is partially migrated. Jetifier will try to rewrite the library anyway.
Example of androidX reference: 'androidx/core/app/NotificationCompat$Builder'
@ -199,6 +214,46 @@ dependencies {
*/
implementation 'com.caverock:androidsvg-aar:1.4'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
implementation "com.google.accompanist:accompanist-flowlayout:0.20.0"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation 'androidx.activity:activity-compose:1.4.0-rc01'
}
repositories {

View File

@ -33,8 +33,9 @@ class TestMisskeyMentionAndroid {
// val a="""[[ ]""".toRegex()
// IDEで警告が出るが、Androidは正規表現エンジンが異なるので仕方ない
@Suppress("RegExpRedundantNestedCharacterClass")
assertEquals(true, """[[ ]]][ ]""".toRegex().matches(" ] "))
}
@Test

View File

@ -1,7 +1,7 @@
package jp.juggler.subwaytooter
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.Host
@ -10,13 +10,13 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.SimpleHttpClientImpl
import jp.juggler.util.LogCategory
import jp.juggler.util.MySslSocketFactory
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@ -38,10 +38,14 @@ class TestTootInstance {
.readTimeout(60.toLong(), TimeUnit.SECONDS)
.writeTimeout(60.toLong(), TimeUnit.SECONDS)
.pingInterval(10, TimeUnit.SECONDS)
.connectionSpecs(Collections.singletonList(ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledCipherSuites()
.allEnabledTlsVersions()
.build()))
.connectionSpecs(
Collections.singletonList(
ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledCipherSuites()
.allEnabledTlsVersions()
.build()
)
)
.sslSocketFactory(MySslSocketFactory, MySslSocketFactory.trustManager)
.build()
@ -56,11 +60,11 @@ class TestTootInstance {
}
}
private val appContext = InstrumentationRegistry.getTargetContext()!!
private val appContext = InstrumentationRegistry.getInstrumentation().targetContext!!
val client = TootApiClient(
context = appContext,
httpClient =SimpleHttpClientImpl(appContext, okHttp),
httpClient = SimpleHttpClientImpl(appContext, okHttp),
callback = dummyClientCallback
)
}
@ -73,12 +77,12 @@ class TestTootInstance {
@Test
fun testWithoutAccount() {
runBlocking {
withContext(Dispatchers.IO){
suspend fun a(host:Host){
val (ti,ri) = TootInstance.get(client,host )
withContext(Dispatchers.IO) {
suspend fun a(host: Host) {
val (ti, ri) = TootInstance.getEx(client, hostArg = host)
assertNotNull(ti)
assertNull(ri?.error)
ti!!.run{ log.d("${instanceType} ${uri} ${version}")}
ti!!.run { log.d("$instanceType $uri $version") }
}
a(Host.parse("mastodon.juggler.jp"))
@ -90,15 +94,15 @@ class TestTootInstance {
@Test
fun testWithAccount() {
runBlocking {
withContext(Dispatchers.IO){
suspend fun a(account:SavedAccount){
val (ti,ri) = TootInstance.get(client,account = account )
withContext(Dispatchers.IO) {
suspend fun a(account: SavedAccount) {
val (ti, ri) = TootInstance.getEx(client, account = account)
assertNull(ri?.error)
assertNotNull(ti)
ti!!.run{ log.d("${account.acct} ${instanceType} ${uri} ${version}")}
ti!!.run { log.d("${account.acct} $instanceType $uri $version") }
}
a( SavedAccount(45,"tateisu@mastodon.juggler.jp") )
a( SavedAccount(45,"tateisu@misskey.io",misskeyVersion=12) )
a(SavedAccount(45, "tateisu@mastodon.juggler.jp"))
a(SavedAccount(45, "tateisu@misskey.io", misskeyVersion = 12))
}
}
}

View File

@ -3,19 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="jp.juggler.subwaytooter">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- CAMERAパーミッションをつけるとPlayストアにプライバシーポリシーを記載する必要がある -->
<!--<uses-permission android:name="android.permission.CAMERA"/>-->
<queries>
<!-- (自アプリ以外で)指定URLを開けるアプリの存在確認 -->
<intent>
<action android:name="android.intent.action.VIEW" />
@ -62,6 +50,20 @@
<!-- </intent>-->
</queries>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- CAMERAパーミッションをつけるとPlayストアにプライバシーポリシーを記載する必要がある -->
<!--<uses-permission android:name="android.permission.CAMERA"/>-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".App1"
android:allowBackup="true"
@ -75,30 +77,11 @@
android:theme="@style/AppTheme.Light"
tools:ignore="UnusedAttribute">
<receiver android:name=".EventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ACTION_MY_PACKAGE_REPLACED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<service
android:name=".notification.PollingService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".notification.PollingForegrounder" />
<activity
android:name=".ActMain"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -108,9 +91,8 @@
<activity
android:name=".ActCallback"
android:label="@string/app_name"
>
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -195,105 +177,122 @@
<activity
android:name=".ActPost"
android:exported="false"
android:label="@string/act_post"
android:windowSoftInputMode="adjustResize" >
android:windowSoftInputMode="adjustResize">
<!--suppress AndroidElementNotAllowed -->
<layout
android:defaultWidth="320dp"
android:defaultHeight="480dp"
android:defaultWidth="320dp"
android:gravity="center"
android:minWidth="64dp"
android:minHeight="64dp"
android:minWidth="64dp"
tools:ignore="UnusedAttribute" />
</activity>
<activity
android:name=".ActAccountSetting"
android:exported="false"
android:label="@string/account_setting"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActAppSetting"
android:exported="false"
android:label="@string/app_setting"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActColumnList"
android:exported="false"
android:label="@string/column_list"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ActAbout"
android:label="@string/app_about"
android:exported="false"
android:label="@string/app_about" />
/>
<activity
android:name=".ActOSSLicense"
android:label="@string/oss_license"
android:exported="false"
android:label="@string/oss_license" />
/>
<activity
android:name=".ActMutedApp"
android:exported="false"
android:label="@string/muted_app" />
<activity
android:name=".ActMutedPseudoAccount"
android:exported="false"
android:label="@string/muted_users_from_pseudo_account" />
<activity
android:name=".ActMutedWord"
android:exported="false"
android:label="@string/muted_word" />
<activity
android:name=".ActFavMute"
android:exported="false"
android:label="@string/fav_muted_user_long" />
<activity
android:name=".ActKeywordFilter"
android:exported="false"
android:label="@string/keyword_filter_new"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActHighlightWordList"
android:exported="false"
android:label="@string/highlight_word" />
<activity
android:name=".ActHighlightWordEdit"
android:exported="false"
android:label="@string/highlight_word" />
<activity
android:name=".ActColumnCustomize"
android:exported="false"
android:label="@string/color_and_background"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActLanguageFilter"
android:exported="false"
android:label="@string/language_filter"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActDrawableList"
android:exported="false"
android:label="@string/drawable_list"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActNickname"
android:exported="false"
android:label="@string/nickname_and_color_and_notification_sound"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActText"
android:exported="false"
android:label="@string/select_and_copy"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".ActMediaViewer"
android:exported="false"
android:theme="@style/AppTheme.Dark.NoActionBar" />
<activity
android:name=".ActExitReasons"
android:exported="false"
android:label="@string/exit_reasons"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
@ -301,6 +300,18 @@
android:name="android.max_aspect"
android:value="100.0" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/Light_colorAccent" />
<meta-data
android:name="android.allow_multiple_resumed_activities"
android:value="true" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="jp.juggler.subwaytooter.FileProvider"
@ -311,38 +322,50 @@
android:resource="@xml/file_provider_path" />
</provider>
<receiver android:name=".DownloadReceiver">
<receiver
android:name=".EventReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ACTION_MY_PACKAGE_REPLACED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".DownloadReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
See README(https://goo.gl/l4GJaQ) for more. -->
<service
android:name=".notification.PollingService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
notification message. See README(https://goo.gl/6BKBk7) for more. -->
<service android:name=".notification.PollingForegrounder" />
<!--https://android-developers.googleblog.com/2018/11/get-your-app-ready-for-foldable-phones.html-->
<service
android:name=".MyFirebaseMessagingService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/Light_colorAccent" />
<!--https://android-developers.googleblog.com/2018/11/get-your-app-ready-for-foldable-phones.html-->
<meta-data
android:name="android.allow_multiple_resumed_activities"
android:value="true" />
</application>
</manifest>
</manifest>

View File

@ -42,8 +42,6 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
internal const val COLOR_DIALOG_ID_ACCT_TEXT = 4
internal const val COLOR_DIALOG_ID_CONTENT_TEXT = 5
internal const val REQUEST_CODE_PICK_BACKGROUND = 1
internal const val PROGRESS_MAX = 65536
fun createIntent(activity: ActMain, idx: Int) =
@ -72,7 +70,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
private var lastImageUri: String? = null
private var lastImageBitmap: Bitmap? = null
val arColumnBackgroundImage = activityResultHandler { ar ->
private val arColumnBackgroundImage = activityResultHandler { ar ->
val data = ar?.data
if (data != null && ar.resultCode == RESULT_OK) {
data.handleGetContentResult(contentResolver)

View File

@ -14,8 +14,6 @@ import android.view.*
import android.view.inputmethod.EditorInfo
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat
import jp.juggler.subwaytooter.action.saveWindowSize
import jp.juggler.subwaytooter.actpost.*
import jp.juggler.subwaytooter.api.*
@ -56,11 +54,6 @@ class ActPost : AppCompatActivity(),
const val KEY_QUOTE = "quote"
const val KEY_SCHEDULED_STATUS = "scheduled_status"
const val KEY_ATTACHMENT_LIST = "attachment_list"
const val KEY_IN_REPLY_TO_ID = "in_reply_to_id"
const val KEY_IN_REPLY_TO_TEXT = "in_reply_to_text"
const val KEY_IN_REPLY_TO_IMAGE = "in_reply_to_image"
const val STATE_ALL = "all"
/////////////////////////////////////////////////
@ -97,8 +90,8 @@ class ActPost : AppCompatActivity(),
lateinit var btnAccount: Button
lateinit var btnVisibility: ImageButton
lateinit var btnAttachment: ImageButton
lateinit var btnPost: ImageButton
private lateinit var btnAttachment: ImageButton
private lateinit var btnPost: ImageButton
lateinit var llAttachment: View
lateinit var ivMedia: List<MyNetworkImageView>
lateinit var cbNSFW: CheckBox
@ -121,7 +114,7 @@ class ActPost : AppCompatActivity(),
lateinit var tvCharCount: TextView
lateinit var handler: Handler
lateinit var formRoot: ActPostRootLinearLayout
private lateinit var formRoot: ActPostRootLinearLayout
lateinit var llReply: View
lateinit var tvReplyTo: TextView
@ -129,8 +122,8 @@ class ActPost : AppCompatActivity(),
lateinit var scrollView: ScrollView
lateinit var tvSchedule: TextView
lateinit var ibSchedule: ImageButton
lateinit var ibScheduleReset: ImageButton
private lateinit var ibSchedule: ImageButton
private lateinit var ibScheduleReset: ImageButton
lateinit var pref: SharedPreferences
lateinit var appState: AppState
@ -184,41 +177,6 @@ class ActPost : AppCompatActivity(),
}
}
val commitContentListener =
InputConnectionCompat.OnCommitContentListener {
inputContentInfo: InputContentInfoCompat,
flags: Int,
_: Bundle?,
->
// Intercepts InputConnection#commitContent API calls.
// - inputContentInfo : content to be committed
// - flags : {@code 0} or {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION}
// - opts : optional bundle data. This can be {@code null}
// return
// - true if this request is accepted by the application,
// no matter if the request is already handled or still being handled in background.
// - false to use the default implementation
// read and display inputContentInfo asynchronously
if (Build.VERSION.SDK_INT >= 25 &&
flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION != 0
) {
try {
inputContentInfo.requestPermission()
} catch (ignored: Exception) {
// return false if failed
return@OnCommitContentListener false
}
}
addAttachment(inputContentInfo.contentUri) {
inputContentInfo.releasePermission()
}
true
}
////////////////////////////////////////////////////////////////
override fun onCreate(savedInstanceState: Bundle?) {
@ -331,7 +289,11 @@ class ActPost : AppCompatActivity(),
openBrowser(span.linkInfo.url)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
attachmentPicker.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
@ -407,7 +369,12 @@ class ActPost : AppCompatActivity(),
updateTextCount()
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
showPoll()
updateTextCount()
}
@ -463,13 +430,17 @@ class ActPost : AppCompatActivity(),
cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
completionHelper = CompletionHelper(this, pref, appState.handler)
completionHelper.attachEditText(formRoot, etContent, false, object : CompletionHelper.Callback2 {
override fun onTextUpdate() {
updateTextCount()
}
completionHelper.attachEditText(
formRoot,
etContent,
false,
object : CompletionHelper.Callback2 {
override fun onTextUpdate() {
updateTextCount()
}
override fun canOpenPopup(): Boolean = true
})
override fun canOpenPopup(): Boolean = true
})
val textWatcher: TextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@ -491,6 +462,6 @@ class ActPost : AppCompatActivity(),
scrollView.viewTreeObserver.addOnScrollChangedListener(scrollListener)
etContent.contentMineTypeArray = AttachmentUploader.acceptableMimeTypes.toTypedArray()
etContent.commitContentListener = commitContentListener
etContent.contentCallback = { addAttachment(it) }
}
}

View File

@ -98,7 +98,7 @@ class ActText : AppCompatActivity() {
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.act_text, menu)
return super.onCreateOptionsMenu(menu)
}

View File

@ -83,9 +83,9 @@ class CompletionHelper(
val c = cp.toChar()
return '0' <= c && c <= '9' ||
'A' <= c && c <= 'Z' ||
'a' <= c && c <= 'z' ||
c == '_' || c == '-' || c == '.'
'A' <= c && c <= 'Z' ||
'a' <= c && c <= 'z' ||
c == '_' || c == '-' || c == '.'
}
// Letter | Mark | Decimal_Number | Connector_Punctuation
@ -348,15 +348,13 @@ class CompletionHelper(
}
})
et.setOnSelectionChangeListener(object : MyEditText.OnSelectionChangeListener {
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
if (selStart != selEnd) {
// 範囲選択されてるならポップアップは閉じる
log.d("onSelectionChanged: range selected")
closeAcctPopup()
}
// 範囲選択されてるならポップアップは閉じる
et.onSelectionChange = { selStart, selEnd ->
if (selStart != selEnd) {
log.d("onSelectionChange: range selected")
closeAcctPopup()
}
})
}
// 全然動いてなさそう…
// et.setCustomSelectionActionModeCallback( action_mode_callback );

View File

@ -105,7 +105,7 @@ open class TootApiResult(
}
// アカウント作成APIのdetailsを読むため、エラー応答のjsonオブジェクトを保持する
var errorJson: JsonObject? = null
private var errorJson: JsonObject? = null
internal fun simplifyErrorHtml(
sv: String,

View File

@ -379,7 +379,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
val result = Channel<Pair<TootInstance?, TootApiResult?>>()
}
fun queuedRequest(
private fun queuedRequest(
allowPixelfed: Boolean,
get: suspend (cached: TootInstance?) -> Pair<TootInstance?, TootApiResult?>,
) = QueuedRequest(allowPixelfed, get)

View File

@ -36,7 +36,7 @@ class TootReaction(
) {
companion object {
fun appendDomain(name: String, domain: String?) =
private fun appendDomain(name: String, domain: String?) =
if (domain?.isNotEmpty() == true) {
"$name@$domain"
} else {
@ -164,7 +164,7 @@ class TootReaction(
}
}
fun chooseUrl() = when {
private fun chooseUrl() = when {
PrefB.bpDisableEmojiAnimation(App1.pref) -> staticUrl
else -> url
}

View File

@ -15,7 +15,7 @@ class ColumnTask_Refresh(
private val bSilent: Boolean,
val bBottom: Boolean,
internal val postedStatusId: EntityId? = null,
internal val refreshAfterToot: Int = -1,
private val refreshAfterToot: Int = -1,
) : ColumnTask(
columnArg,
if (bBottom) ColumnTaskType.REFRESH_BOTTOM else ColumnTaskType.REFRESH_TOP

View File

@ -18,7 +18,7 @@ class UserRelationLoader(val column: Column) {
val whoSet = HashSet<EntityId>()
val acctSet = HashSet<String>()
val tagSet = HashSet<String>()
private val tagSet = HashSet<String>()
fun add(whoRef: TootAccountRef?) {
add(whoRef?.get())

View File

@ -80,7 +80,7 @@ class ColumnViewHolder(
lateinit var llColumnHeader: View
lateinit var tvColumnIndex: TextView
lateinit var tvColumnStatus: TextView
lateinit var tvColumnContext: TextView
private lateinit var tvColumnContext: TextView
lateinit var ivColumnIcon: ImageView
lateinit var tvColumnName: TextView
@ -143,7 +143,7 @@ class ColumnViewHolder(
lateinit var btnQuickFilterVote: ImageButton
lateinit var llRefreshError: FrameLayout
lateinit var ivRefreshError: ImageView
private lateinit var ivRefreshError: ImageView
lateinit var tvRefreshError: TextView
lateinit var llListList: View

View File

@ -552,7 +552,7 @@ internal class DlgContextMenu(
return true
}
fun onClickUpdateGroup(v: View): Boolean = when (v.id) {
private fun onClickUpdateGroup(v: View): Boolean = when (v.id) {
R.id.btnGroupStatusCrossAccount -> updateGroup(
btnGroupStatusCrossAccount,
llGroupStatusCrossAccount,

View File

@ -775,7 +775,7 @@ class ItemViewHolder(
}
}
fun _LinearLayout.inflateConversationIconOne() =
private fun _LinearLayout.inflateConversationIconOne() =
myNetworkImageView {
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(dip(24), dip(24)) {

View File

@ -324,7 +324,7 @@ fun ItemViewHolder.onClickEnqueteChoice(
TootPollsType.Mastodon -> client.request(
"/api/v1/polls/${enquete.pollId}/votes",
jsonObject {
put("choices", jp.juggler.util.jsonArray { add(idx) })
put("choices", jsonArray { add(idx) })
}.toPostRequestBuilder()
)
TootPollsType.FriendsNico -> client.request(
@ -406,7 +406,7 @@ fun ItemViewHolder.sendMultiple(
client.request(
"/api/v1/polls/${enquete.pollId}/votes",
jsonObject {
put("choices", jp.juggler.util.jsonArray {
put("choices", jsonArray {
enquete.items.forEachIndexed { index, choice ->
if (choice.checked) add(index)
}

View File

@ -84,8 +84,8 @@ class StatusButtons(
private val colorAccent: Int
get() = activity.attrColor(R.attr.colorImageButtonAccent)
var optionalButtonFirst: View? = null
var optionalButtonCount = 0
private var optionalButtonFirst: View? = null
private var optionalButtonCount = 0
var ti: TootInstance? = null
init {

View File

@ -472,7 +472,7 @@ class PushSubscriptionHelper(
}
}
suspend fun canSkipSubscriptionMastodon(
private suspend fun canSkipSubscriptionMastodon(
client: TootApiClient,
clientIdentifier: String,
endpoint: String,

View File

@ -3,7 +3,7 @@ package jp.juggler.subwaytooter.span
import android.text.TextPaint
import android.text.style.CharacterStyle
class HighlightSpan(val colorFg: Int, val colorBg: Int) : CharacterStyle() {
class HighlightSpan(private val colorFg: Int, val colorBg: Int) : CharacterStyle() {
override fun updateDrawState(ds: TextPaint) {
if (colorFg != 0) ds.color = colorFg

View File

@ -31,7 +31,7 @@ class MyClickableSpan(val linkInfo: LinkInfo) : ClickableSpan() {
var showLinkUnderline = true
}
val colorFg: Int
private val colorFg: Int
val colorBg: Int
init {

View File

@ -223,7 +223,7 @@ class AttachmentPicker(
}
}
fun performCapture(action: String, errorCaption: String) {
private fun performCapture(action: String, errorCaption: String) {
try {
arCapture.launch(Intent(action))
} catch (ex: Throwable) {

View File

@ -69,10 +69,10 @@ class PostImpl(
private var visibilityChecked: TootVisibility? = null
var bConfirmTag: Boolean = false
private var bConfirmTag: Boolean = false
var bConfirmAccount: Boolean = false
var bConfirmRedraft: Boolean = false
var bConfirmTagCharacter: Boolean = false
private var bConfirmRedraft: Boolean = false
private var bConfirmTagCharacter: Boolean = false
private val choiceMaxChars = when {
account.isMisskey -> 15
@ -229,9 +229,9 @@ class PostImpl(
return true
}
var resultStatus: TootStatus? = null
var resultCredentialTmp: TootAccount? = null
var resultScheduledStatusSucceeded = false
private var resultStatus: TootStatus? = null
private var resultCredentialTmp: TootAccount? = null
private var resultScheduledStatusSucceeded = false
private suspend fun getCredential(
client: TootApiClient,

View File

@ -2,23 +2,53 @@ package jp.juggler.subwaytooter.view
import android.annotation.SuppressLint
import android.content.Context
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.appcompat.widget.AppCompatEditText
import android.net.Uri
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.View
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.ViewCompat
import jp.juggler.util.LogCategory
class MyEditText : AppCompatEditText {
companion object {
private val log = LogCategory("MyEditText")
val MIME_TYPES = arrayOf("image/*")
}
private var mOnSelectionChangeListener: OnSelectionChangeListener? = null
// 選択範囲変更リスナ
var onSelectionChange: ((selStart: Int, selEnd: Int) -> Unit)? = null
// キーボードやDnDから画像を挿入するリスナ
var contentCallback: ((Uri) -> Unit)? = null
///////////////////////////////////////////////////////
// IMEから画像を送られてくることがあるらしい
var contentMineTypeArray: Array<String>? = null
private val receiveContentListener = object : OnReceiveContentListener {
override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat {
// 受け付けない状況では何も受け取らずに残りを返す
val contentCallback = contentCallback ?: return payload
val pair = payload.partition { item -> item.uri != null }
val uriContent = pair.first
val remaining = pair.second
if (uriContent != null) {
val clip = uriContent.clip
for (i in 0 until clip.itemCount) {
val uri = clip.getItemAt(i).uri
contentCallback(uri)
}
}
return remaining
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
@ -28,20 +58,16 @@ class MyEditText : AppCompatEditText {
defStyleAttr
)
init {
ViewCompat.setOnReceiveContentListener(this, MIME_TYPES, receiveContentListener)
}
////////////////////////////////////////////////////
// 選択範囲変更イベントをコールバックに渡す
interface OnSelectionChangeListener {
fun onSelectionChanged(selStart: Int, selEnd: Int)
}
fun setOnSelectionChangeListener(listener: OnSelectionChangeListener) {
mOnSelectionChangeListener = listener
}
// 選択範囲変更の傍受
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
super.onSelectionChanged(selStart, selEnd)
mOnSelectionChangeListener?.onSelectionChanged(selStart, selEnd)
onSelectionChange?.invoke(selStart, selEnd)
}
////////////////////////////////////////////////////
@ -61,26 +87,4 @@ class MyEditText : AppCompatEditText {
// at android.view.View.dispatchTouchEvent (View.java:9303)
}
}
///////////////////////////////////////////////////////
// IMEから画像を送られてくることがあるらしい
var commitContentListener: InputConnectionCompat.OnCommitContentListener? = null
var contentMineTypeArray: Array<String>? = null
override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection? {
log.d("onCreateInputConnection: listener=$commitContentListener")
val superIc = super.onCreateInputConnection(outAttrs)
val listener = commitContentListener
val mimeArray = contentMineTypeArray
return if (listener == null || mimeArray == null || outAttrs == null) {
superIc
} else {
EditorInfoCompat.setContentMimeTypes(outAttrs, mimeArray)
superIc?.let { InputConnectionCompat.createWrapper(it, outAttrs, listener) }
}
}
}

View File

@ -3,6 +3,7 @@ package jp.juggler.util
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import androidx.annotation.IntRange
/////////////////////////////////////////////////////////////
// SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる
@ -19,16 +20,21 @@ fun Int.i2b() = this != 0
// getBoolean(getColumnIndex(key))
fun Cursor.getInt(key: String) =
getInt(getColumnIndex(key))
getColumnIndex(key).takeIf { it >= 0 }?.let { getInt(it) }
?: error("getInt: missing column named $key")
fun Cursor.getIntOrNull(idx: Int) =
if (isNull(idx)) null else getInt(idx)
fun Cursor.getIntOrNull(@IntRange(from = 0) idx: Int) = when {
idx < 0 -> error("getIntOrNull: invalid index $idx")
isNull(idx) -> null
else -> getInt(idx)
}
fun Cursor.getIntOrNull(key: String) =
getIntOrNull(getColumnIndex(key))
getColumnIndex(key).takeIf { it >= 0 }?.let { getIntOrNull(it) }
fun Cursor.getLong(key: String) =
getLong(getColumnIndex(key))
getColumnIndex(key).takeIf { it >= 0 }?.let { getLong(it) }
?: error("getLong: missing column named $key")
//fun Cursor.getLongOrNull(idx:Int) =
// if(isNull(idx)) null else getLong(idx)
@ -37,7 +43,8 @@ fun Cursor.getLong(key: String) =
// getLongOrNull(getColumnIndex(key))
fun Cursor.getString(key: String): String =
getString(getColumnIndex(key))
getColumnIndex(key).takeIf { it >= 0 }?.let { getString(it)!! }
?: error("getString: missing column named $key")
fun Cursor.getStringOrNull(keyIdx: Int) =
if (isNull(keyIdx)) null else getString(keyIdx)
@ -69,7 +76,7 @@ class ColumnMeta(
class List(
val table: String,
val initialVersion: Int,
private val initialVersion: Int,
var createExtra: () -> Array<String> = { emptyArray() },
var deleteBeforeCreate: Boolean = false,
) : ArrayList<ColumnMeta>() {
@ -143,15 +150,9 @@ fun ContentValues.put(key: ColumnMeta, v: Float?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: Double?) = put(key.name, v)
fun ContentValues.put(key: ColumnMeta, v: ByteArray?) = put(key.name, v)
fun Cursor.getInt(key: ColumnMeta) = getInt(getColumnIndex(key.name))
fun Cursor.getBoolean(key: ColumnMeta) = getInt(key).i2b()
fun Cursor.getLong(key: ColumnMeta) = getLong(getColumnIndex(key.name))
@Suppress("unused")
fun Cursor.getIntOrNull(key: ColumnMeta) = getIntOrNull(getColumnIndex(key.name))
fun Cursor.getString(key: ColumnMeta): String = getString(getColumnIndex(key.name))
fun Cursor.getStringOrNull(key: ColumnMeta): String? {
val idx = key.getIndex(this)
return if (isNull(idx)) null else getString(idx)
}
fun Cursor.getInt(key: ColumnMeta) = getInt(key.name)
fun Cursor.getBoolean(key: ColumnMeta) = getInt(key.name).i2b()
fun Cursor.getLong(key: ColumnMeta) = getLong(key.name)
fun Cursor.getIntOrNull(key: ColumnMeta) = getIntOrNull(key.name)
fun Cursor.getString(key: ColumnMeta): String = getString(key.name)
fun Cursor.getStringOrNull(key: ColumnMeta): String? = getStringOrNull(key.name)

View File

@ -23,7 +23,7 @@ class LogCategory(category: String) {
///////////////////////////////
// string
fun msg(priority: Int, msg: String): Boolean {
private fun msg(priority: Int, msg: String): Boolean {
Log.println(priority, tag, msg)
return false
}
@ -37,7 +37,7 @@ class LogCategory(category: String) {
///////////////////////////////
// Resources.getString()
fun msg(priority: Int, res: Resources, @StringRes stringId: Int, args: Array<out Any?>) =
private fun msg(priority: Int, res: Resources, @StringRes stringId: Int, args: Array<out Any?>) =
msg(priority, res.getString(stringId, *args))
fun e(res: Resources, @StringRes stringId: Int, vararg args: Any) =
@ -58,7 +58,7 @@ class LogCategory(category: String) {
///////////////////////////////
// Throwable + string
fun msg(priority: Int, ex: Throwable, caption: String = "exception.") =
private fun msg(priority: Int, ex: Throwable, caption: String = "exception.") =
msg(priority, ex.withCaption(caption))
fun e(ex: Throwable, caption: String = "exception") = msg(Log.ERROR, ex, caption)

View File

@ -1,19 +1,22 @@
buildscript {
ext.min_sdk_version = 21
ext.target_sdk_version = 30
ext.compile_sdk_version = 30
ext.target_sdk_version = 31
ext.compile_sdk_version = 31
ext.appcompat_version='1.3.0'
ext.lifecycle_version='2.3.1'
ext.appcompat_version='1.3.1'
ext.lifecycle_version="2.4.0-rc01"
ext.arch_version = "2.1.0"
ext.kotlin_version = '1.5.20'
ext.kotlinx_coroutines_version = '1.5.0'
ext.kotlin_version = '1.5.31'
ext.kotlinx_coroutines_version = '1.5.2'
ext.anko_version='0.10.8'
ext.junit_version='4.13.2'
ext.detekt_version='1.18.0'
ext.detekt_version='1.18.1'
ext.compose_version = '1.0.4'
repositories {
google()
@ -21,7 +24,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.google.gms:google-services:4.3.10'
//noinspection DifferentKotlinGradleVersion

View File

@ -32,82 +32,87 @@ import androidx.annotation.NonNull;
* It's the pattern you will often see as a background behind a partly transparent image in many applications.
*/
class AlphaPatternDrawable extends Drawable {
private int rectangleSize;
private Paint paint = new Paint();
private Paint paintWhite = new Paint();
private Paint paintGray = new Paint();
private int numRectanglesHorizontal;
private int numRectanglesVertical;
/**
* 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 Bitmap bitmap;
AlphaPatternDrawable( int rectangleSize ){
this.rectangleSize = rectangleSize;
paintWhite.setColor( 0xFFFFFFFF );
paintGray.setColor( 0xFFCBCBCB );
}
@Override public void draw( @NonNull Canvas canvas ){
if( bitmap != null && ! bitmap.isRecycled() ){
canvas.drawBitmap( bitmap, null, getBounds(), paint );
}
}
@Override public int getOpacity(){
return PixelFormat.UNKNOWN;
}
@Override public void setAlpha( int alpha ){
throw new UnsupportedOperationException( "Alpha is not supported by this drawable." );
}
@Override public void setColorFilter( ColorFilter cf ){
throw new UnsupportedOperationException( "ColorFilter is not supported by this drawable." );
}
@Override protected void onBoundsChange( Rect bounds ){
super.onBoundsChange( bounds );
int height = bounds.height();
int width = bounds.width();
numRectanglesHorizontal = (int) Math.ceil( width / (float) rectangleSize );
numRectanglesVertical = (int) Math.ceil( height / (float) rectangleSize );
generatePatternBitmap();
}
/**
* 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 void generatePatternBitmap(){
if( getBounds().width() <= 0 || getBounds().height() <= 0 ){
return;
}
bitmap = Bitmap.createBitmap( getBounds().width(), getBounds().height(), Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
Rect r = new Rect();
boolean verticalStartWhite = true;
for( int i = 0 ; i <= numRectanglesVertical ; i++ ){
boolean isWhite = verticalStartWhite;
for( int j = 0 ; j <= numRectanglesHorizontal ; j++ ){
r.top = i * rectangleSize;
r.left = j * rectangleSize;
r.bottom = r.top + rectangleSize;
r.right = r.left + rectangleSize;
canvas.drawRect( r, isWhite ? paintWhite : paintGray );
isWhite = ! isWhite;
}
verticalStartWhite = ! verticalStartWhite;
}
}
private final int rectangleSize;
private final Paint paint = new Paint();
private final Paint paintWhite = new Paint();
private final Paint paintGray = new Paint();
private int numRectanglesHorizontal;
private int numRectanglesVertical;
/**
* 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 Bitmap bitmap;
AlphaPatternDrawable(int rectangleSize) {
this.rectangleSize = rectangleSize;
paintWhite.setColor(0xFFFFFFFF);
paintGray.setColor(0xFFCBCBCB);
}
@Override
public void draw(@NonNull Canvas canvas) {
if (bitmap != null && !bitmap.isRecycled()) {
canvas.drawBitmap(bitmap, null, getBounds(), paint);
}
}
@Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
@Override
public void setAlpha(int alpha) {
throw new UnsupportedOperationException("Alpha is not supported by this drawable.");
}
@Override
public void setColorFilter(ColorFilter cf) {
throw new UnsupportedOperationException("ColorFilter is not supported by this drawable.");
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
int height = bounds.height();
int width = bounds.width();
numRectanglesHorizontal = (int) Math.ceil(width / (float) rectangleSize);
numRectanglesVertical = (int) Math.ceil(height / (float) rectangleSize);
generatePatternBitmap();
}
/**
* 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 void generatePatternBitmap() {
if (getBounds().width() <= 0 || getBounds().height() <= 0) {
return;
}
bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Rect r = new Rect();
boolean verticalStartWhite = true;
for (int i = 0; i <= numRectanglesVertical; i++) {
boolean isWhite = verticalStartWhite;
for (int j = 0; j <= numRectanglesHorizontal; j++) {
r.top = i * rectangleSize;
r.left = j * rectangleSize;
r.bottom = r.top + rectangleSize;
r.right = r.left + rectangleSize;
canvas.drawRect(r, isWhite ? paintWhite : paintGray);
isWhite = !isWhite;
}
verticalStartWhite = !verticalStartWhite;
}
}
}

View File

@ -19,129 +19,130 @@ package com.jrummyapps.android.colorpicker;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import androidx.core.graphics.ColorUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import androidx.core.graphics.ColorUtils;
class ColorPaletteAdapter extends BaseAdapter {
/*package*/ final OnColorSelectedListener listener;
/*package*/ final int[] colors;
/*package*/ int selectedPosition;
/*package*/ int colorShape;
/*package*/ final OnColorSelectedListener listener;
/*package*/ final int[] colors;
/*package*/ int selectedPosition;
/*package*/ final int colorShape;
ColorPaletteAdapter(OnColorSelectedListener listener,
int[] colors,
int selectedPosition,
@ColorShape int colorShape) {
this.listener = listener;
this.colors = colors;
this.selectedPosition = selectedPosition;
this.colorShape = colorShape;
}
@Override public int getCount() {
return colors.length;
}
@Override public Object getItem(int position) {
return colors[position];
}
@Override public long getItemId(int position) {
return position;
}
@Override public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder(parent.getContext());
convertView = holder.view;
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.setup(position);
return convertView;
}
void selectNone() {
selectedPosition = -1;
notifyDataSetChanged();
}
interface OnColorSelectedListener {
void onColorSelected(int color);
}
private final class ViewHolder {
View view;
ColorPanelView colorPanelView;
ImageView imageView;
int originalBorderColor;
ViewHolder(Context context) {
int layoutResId;
if (colorShape == ColorShape.SQUARE) {
layoutResId = R.layout.cpv_color_item_square;
} else {
layoutResId = R.layout.cpv_color_item_circle;
}
view = View.inflate(context, layoutResId, null);
colorPanelView = (ColorPanelView) view.findViewById(R.id.cpv_color_panel_view);
imageView = (ImageView) view.findViewById(R.id.cpv_color_image_view);
originalBorderColor = colorPanelView.getBorderColor();
view.setTag(this);
ColorPaletteAdapter(OnColorSelectedListener listener,
int[] colors,
int selectedPosition,
@ColorShape int colorShape) {
this.listener = listener;
this.colors = colors;
this.selectedPosition = selectedPosition;
this.colorShape = colorShape;
}
void setup(int position) {
int color = colors[position];
int alpha = Color.alpha(color);
colorPanelView.setColor(color);
imageView.setImageResource(selectedPosition == position ? R.drawable.cpv_preset_checked : 0);
if (alpha != 255) {
if (alpha <= ColorPickerDialog.ALPHA_THRESHOLD) {
colorPanelView.setBorderColor(color | 0xFF000000);
imageView.setColorFilter(/*color | 0xFF000000*/Color.BLACK, PorterDuff.Mode.SRC_IN);
@Override
public int getCount() {
return colors.length;
}
@Override
public Object getItem(int position) {
return colors[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder(parent.getContext());
convertView = holder.view;
} else {
colorPanelView.setBorderColor(originalBorderColor);
imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
holder = (ViewHolder) convertView.getTag();
}
} else {
setColorFilter(position);
}
setOnClickListener(position);
holder.setup(position);
return convertView;
}
private void setOnClickListener(final int position) {
colorPanelView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (selectedPosition != position) {
selectedPosition = position;
notifyDataSetChanged();
}
listener.onColorSelected(colors[position]);
}
});
colorPanelView.setOnLongClickListener(new View.OnLongClickListener() {
@Override public boolean onLongClick(View v) {
colorPanelView.showHint();
return true;
}
});
void selectNone() {
selectedPosition = -1;
notifyDataSetChanged();
}
private void setColorFilter(int position) {
if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) {
imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
} else {
imageView.setColorFilter(null);
}
interface OnColorSelectedListener {
void onColorSelected(int color);
}
}
private final class ViewHolder {
final View view;
final ColorPanelView colorPanelView;
final ImageView imageView;
final int originalBorderColor;
ViewHolder(Context context) {
int layoutResId;
if (colorShape == ColorShape.SQUARE) {
layoutResId = R.layout.cpv_color_item_square;
} else {
layoutResId = R.layout.cpv_color_item_circle;
}
view = View.inflate(context, layoutResId, null);
colorPanelView = view.findViewById(R.id.cpv_color_panel_view);
imageView = view.findViewById(R.id.cpv_color_image_view);
originalBorderColor = colorPanelView.getBorderColor();
view.setTag(this);
}
void setup(int position) {
int color = colors[position];
int alpha = Color.alpha(color);
colorPanelView.setColor(color);
imageView.setImageResource(selectedPosition == position ? R.drawable.cpv_preset_checked : 0);
if (alpha != 255) {
if (alpha <= ColorPickerDialog.ALPHA_THRESHOLD) {
colorPanelView.setBorderColor(color | 0xFF000000);
imageView.setColorFilter(/*color | 0xFF000000*/Color.BLACK, PorterDuff.Mode.SRC_IN);
} else {
colorPanelView.setBorderColor(originalBorderColor);
imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
}
} else {
setColorFilter(position);
}
setOnClickListener(position);
}
private void setOnClickListener(final int position) {
colorPanelView.setOnClickListener(v -> {
if (selectedPosition != position) {
selectedPosition = position;
notifyDataSetChanged();
}
listener.onColorSelected(colors[position]);
});
colorPanelView.setOnLongClickListener(v -> {
colorPanelView.showHint();
return true;
});
}
private void setColorFilter(int position) {
if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) {
imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
} else {
imageView.setColorFilter(null);
}
}
}
}

View File

@ -141,22 +141,22 @@ public class ColorPanelView extends View {
} else if (shape == ColorShape.CIRCLE) {
final int outerRadius = getMeasuredWidth() / 2;
if (borderWidthPx > 0) {
canvas.drawCircle(getMeasuredWidth() / 2,
getMeasuredHeight() / 2,
canvas.drawCircle(getMeasuredWidth() / 2f,
getMeasuredHeight() / 2f,
outerRadius,
borderPaint);
}
if (Color.alpha(color) < 255) {
canvas.drawCircle(getMeasuredWidth() / 2,
getMeasuredHeight() / 2,
canvas.drawCircle(getMeasuredWidth() / 2f,
getMeasuredHeight() / 2f,
outerRadius - borderWidthPx, alphaPaint);
}
if (showOldColor) {
canvas.drawArc(centerRect, 90, 180, true, originalPaint);
canvas.drawArc(centerRect, 270, 180, true, colorPaint);
} else {
canvas.drawCircle(getMeasuredWidth() / 2,
getMeasuredHeight() / 2,
canvas.drawCircle(getMeasuredWidth() / 2f,
getMeasuredHeight() / 2f,
outerRadius - borderWidthPx,
colorPaint);
}
@ -169,6 +169,7 @@ public class ColorPanelView extends View {
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
} else if (shape == ColorShape.CIRCLE) {
//noinspection SuspiciousNameCombination
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
} else {

View File

@ -16,199 +16,217 @@
package com.jrummyapps.android.colorpicker;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.preference.Preference;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import com.jrummyapps.android.colorpicker.ColorPickerDialog.DialogType;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
/**
* A Preference to select a color
*/
public class ColorPreference extends Preference implements ColorPickerDialogListener {
private static final int SIZE_NORMAL = 0;
private static final int SIZE_LARGE = 1;
private static final int SIZE_NORMAL = 0;
private static final int SIZE_LARGE = 1;
private OnShowDialogListener onShowDialogListener;
private int color = Color.BLACK;
private boolean showDialog;
@DialogType
private int dialogType;
private int colorShape;
private boolean allowPresets;
private boolean allowCustom;
private boolean showAlphaSlider;
private boolean showColorShades;
private int previewSize;
private int[] presets;
private int dialogTitle;
private OnShowDialogListener onShowDialogListener;
private int color = Color.BLACK;
private boolean showDialog;
@DialogType
private int dialogType;
private int colorShape;
private boolean allowPresets;
private boolean allowCustom;
private boolean showAlphaSlider;
private boolean showColorShades;
private int previewSize;
private int[] presets;
private int dialogTitle;
public ColorPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ColorPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
setPersistent(true);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPreference);
showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true);
//noinspection WrongConstant
dialogType = a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS);
colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE);
allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true);
allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true);
showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false);
showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true);
previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, SIZE_NORMAL);
final int presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0);
dialogTitle = a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title);
if (presetsResId != 0) {
presets = getContext().getResources().getIntArray(presetsResId);
} else {
presets = ColorPickerDialog.MATERIAL_COLORS;
public ColorPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
if (colorShape == ColorShape.CIRCLE) {
setWidgetLayoutResource(
previewSize == SIZE_LARGE ? R.layout.cpv_preference_circle_large : R.layout.cpv_preference_circle);
} else {
setWidgetLayoutResource(
previewSize == SIZE_LARGE ? R.layout.cpv_preference_square_large : R.layout.cpv_preference_square
);
public ColorPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
a.recycle();
}
@Override protected void onClick() {
super.onClick();
if (onShowDialogListener != null) {
onShowDialogListener.onShowColorPickerDialog((String) getTitle(), color);
} else if (showDialog) {
ColorPickerDialog dialog = ColorPickerDialog.newBuilder()
.setDialogType(dialogType)
.setDialogTitle(dialogTitle)
.setColorShape(colorShape)
.setPresets(presets)
.setAllowPresets(allowPresets)
.setAllowCustom(allowCustom)
.setShowAlphaSlider(showAlphaSlider)
.setShowColorShades(showColorShades)
.setColor(color)
.create();
dialog.setColorPickerDialogListener(ColorPreference.this);
Activity activity = (Activity) getContext();
dialog.show(activity.getFragmentManager(), getFragmentTag());
private void init(AttributeSet attrs) {
setPersistent(true);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPreference);
showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true);
//noinspection WrongConstant
dialogType = a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS);
colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE);
allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true);
allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true);
showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false);
showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true);
previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, SIZE_NORMAL);
final int presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0);
dialogTitle = a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title);
if (presetsResId != 0) {
presets = getContext().getResources().getIntArray(presetsResId);
} else {
presets = ColorPickerDialog.MATERIAL_COLORS;
}
if (colorShape == ColorShape.CIRCLE) {
setWidgetLayoutResource(
previewSize == SIZE_LARGE ? R.layout.cpv_preference_circle_large : R.layout.cpv_preference_circle);
} else {
setWidgetLayoutResource(
previewSize == SIZE_LARGE ? R.layout.cpv_preference_square_large : R.layout.cpv_preference_square
);
}
a.recycle();
}
}
@Override protected void onAttachedToActivity() {
super.onAttachedToActivity();
if (showDialog) {
Activity activity = (Activity) getContext();
ColorPickerDialog fragment =
(ColorPickerDialog) activity.getFragmentManager().findFragmentByTag(getFragmentTag());
if (fragment != null) {
// re-bind preference to fragment
fragment.setColorPickerDialogListener(this);
}
@Override
protected void onClick() {
super.onClick();
if (onShowDialogListener != null) {
onShowDialogListener.onShowColorPickerDialog((String) getTitle(), color);
} else if (showDialog) {
ColorPickerDialog dialog = ColorPickerDialog.newBuilder()
.setDialogType(dialogType)
.setDialogTitle(dialogTitle)
.setColorShape(colorShape)
.setPresets(presets)
.setAllowPresets(allowPresets)
.setAllowCustom(allowCustom)
.setShowAlphaSlider(showAlphaSlider)
.setShowColorShades(showColorShades)
.setColor(color)
.create();
dialog.setColorPickerDialogListener(ColorPreference.this);
FragmentManager fm = getFragmentManager();
if (fm != null) {
dialog.show(fm, getFragmentTag());
}
}
}
}
@Override protected void onBindView(View view) {
super.onBindView(view);
ColorPanelView preview = (ColorPanelView) view.findViewById(R.id.cpv_preference_preview_color_panel);
if (preview != null) {
preview.setColor(color);
@Nullable
private FragmentManager getFragmentManager() {
Context context = getContext();
if (context instanceof FragmentActivity) {
return ((FragmentActivity) context).getSupportFragmentManager();
}
return null;
}
}
@Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
color = getPersistedInt(0xFF000000);
} else {
color = (Integer) defaultValue;
persistInt(color);
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
FragmentManager fm = getFragmentManager();
if (showDialog && fm != null) {
ColorPickerDialog fragment = (ColorPickerDialog) fm.findFragmentByTag(getFragmentTag());
if (fragment != null) {
// re-bind preference to fragment
fragment.setColorPickerDialogListener(this);
}
}
}
}
@Override protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInteger(index, Color.BLACK);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
ColorPanelView preview = view.findViewById(R.id.cpv_preference_preview_color_panel);
if (preview != null) {
preview.setColor(color);
}
}
@Override public void onColorSelected(int dialogId, @ColorInt int color) {
saveValue(color);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {
color = getPersistedInt(0xFF000000);
} else {
color = (Integer) defaultValue;
persistInt(color);
}
}
@Override public void onDialogDismissed(int dialogId) {
// no-op
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInteger(index, Color.BLACK);
}
/**
* Set the new color
*
* @param color
* The newly selected color
*/
public void saveValue(@ColorInt int color) {
this.color = color;
persistInt(this.color);
notifyChanged();
callChangeListener(color);
}
@Override
public void onColorSelected(int dialogId, @ColorInt int color) {
saveValue(color);
}
/**
* Set the colors shown in the {@link ColorPickerDialog}.
*
* @param presets An array of color ints
*/
public void setPresets(@NonNull int[] presets) {
this.presets = presets;
}
@Override
public void onDialogDismissed(int dialogId) {
// no-op
}
/**
* Get the colors that will be shown in the {@link ColorPickerDialog}.
*
* @return An array of color ints
*/
public int[] getPresets() {
return presets;
}
/**
* Set the new color
*
* @param color The newly selected color
*/
public void saveValue(@ColorInt int color) {
this.color = color;
persistInt(this.color);
notifyChanged();
callChangeListener(color);
}
/**
* The listener used for showing the {@link ColorPickerDialog}.
* Call {@link #saveValue(int)} after the user chooses a color.
* If this is set then it is up to you to show the dialog.
*
* @param listener
* The listener to show the dialog
*/
public void setOnShowDialogListener(OnShowDialogListener listener) {
onShowDialogListener = listener;
}
/**
* Set the colors shown in the {@link ColorPickerDialog}.
*
* @param presets An array of color ints
*/
public void setPresets(@NonNull int[] presets) {
this.presets = presets;
}
/**
* The tag used for the {@link ColorPickerDialog}.
*
* @return The tag
*/
public String getFragmentTag() {
return "color_" + getKey();
}
/**
* Get the colors that will be shown in the {@link ColorPickerDialog}.
*
* @return An array of color ints
*/
public int[] getPresets() {
return presets;
}
public interface OnShowDialogListener {
/**
* The listener used for showing the {@link ColorPickerDialog}.
* Call {@link #saveValue(int)} after the user chooses a color.
* If this is set then it is up to you to show the dialog.
*
* @param listener The listener to show the dialog
*/
public void setOnShowDialogListener(OnShowDialogListener listener) {
onShowDialogListener = listener;
}
void onShowColorPickerDialog(String title, int currentColor);
}
/**
* The tag used for the {@link ColorPickerDialog}.
*
* @return The tag
*/
public String getFragmentTag() {
return "color_" + getKey();
}
public interface OnShowDialogListener {
void onShowColorPickerDialog(String title, int currentColor);
}
}

View File

@ -1,6 +1,5 @@
#Wed May 05 20:20:56 JST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -44,10 +44,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
testImplementation "junit:junit:$junit_version"
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
}

View File

@ -1,29 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.juggler.apng.sample"
>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="jp.juggler.apng.sample">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<application
android:allowBackup="true"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
android:theme="@style/AppTheme">
<activity android:name=".ActList">
<activity
android:name=".ActList"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ActViewer"/>
<activity android:name=".ActViewer" />
</application>

View File

@ -78,21 +78,12 @@ class ActList : AppCompatActivity(), CoroutineScope {
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
PERMISSION_REQUEST_CODE_STORAGE -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return
if( requestCode == PERMISSION_REQUEST_CODE_STORAGE){
if (grantResults.all{ it == PackageManager.PERMISSION_GRANTED }) {
// 特に何もしてないらしい
}
// other 'case' lines to check for other
// permissions this app might request
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun load() = launch {

View File

@ -117,7 +117,7 @@ class ActViewer : AppCompatActivity() , CoroutineScope {
dir.mkdirs()
if(! dir.exists() ) {
Log.e(TAG, "Directory not exists: ${dir}")
Log.e(TAG, "Directory not exists: $dir")
return@launch
}
val frames = apngFrames.frames
@ -127,7 +127,7 @@ class ActViewer : AppCompatActivity() , CoroutineScope {
}
var i=0
for( f in frames) {
Log.d(TAG, "${title}[${i}] timeWidth=${f.timeWidth}")
Log.d(TAG, "$title[$i] timeWidth=${f.timeWidth}")
val bitmap = f.bitmap
FileOutputStream( File(dir,"${title}_${i}.png")).use{ fo ->