add CryptUrils. import anko source code instead of aar library to avoid build problem
This commit is contained in:
parent
fb59239edd
commit
492995dc40
|
@ -3,6 +3,7 @@
|
|||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="apng" target="1.7" />
|
||||
<module name="SubwayTooter.anko" target="11" />
|
||||
<module name="SubwayTooter.apng_android" target="11" />
|
||||
<module name="SubwayTooter.app" target="11" />
|
||||
<module name="SubwayTooter.base" target="11" />
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/anko" />
|
||||
<option value="$PROJECT_DIR$/apng" />
|
||||
<option value="$PROJECT_DIR$/apng_android" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" />
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,48 @@
|
|||
plugins {
|
||||
id "com.android.library"
|
||||
id "org.jetbrains.kotlin.android"
|
||||
id "org.jetbrains.kotlin.kapt"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "jp.juggler.anko"
|
||||
compileSdk stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk stMinSdkVersion
|
||||
targetSdk stTargetSdkVersion
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = kotlinJvmTarget
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation "androidx.core:core-ktx:$coreKtxVersion"
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "com.google.android.material:material:$materialVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxTestExtJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
|
||||
|
||||
implementation "androidx.preference:preference:$preferenceVersion"
|
||||
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||
|
||||
kapt "androidx.annotation:annotation:$androidxAnnotationVersion"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package jp.juggler.anko
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("jp.juggler.anko.test", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.ViewManager
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
@SuppressLint("SupportAnnotationUsage")
|
||||
interface AlertBuilder<out D : DialogInterface> {
|
||||
val ctx: Context
|
||||
|
||||
var title: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var titleResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var message: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var messageResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var icon: Drawable
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
@setparam:DrawableRes
|
||||
var iconResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var customTitle: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var customView: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
var isCancelable: Boolean
|
||||
@Deprecated(NO_GETTER, level = ERROR) get
|
||||
|
||||
fun onCancelled(handler: (dialog: DialogInterface) -> Unit)
|
||||
|
||||
fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean)
|
||||
|
||||
fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
|
||||
fun positiveButton(
|
||||
@StringRes buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
)
|
||||
|
||||
fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
|
||||
fun negativeButton(
|
||||
@StringRes buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
)
|
||||
|
||||
fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit)
|
||||
fun neutralPressed(
|
||||
@StringRes buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
)
|
||||
|
||||
fun items(
|
||||
items: List<CharSequence>,
|
||||
onItemSelected: (dialog: DialogInterface, index: Int) -> Unit,
|
||||
)
|
||||
|
||||
fun <T> items(
|
||||
items: List<T>,
|
||||
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit,
|
||||
)
|
||||
|
||||
fun build(): D
|
||||
fun show(): D
|
||||
}
|
||||
|
||||
fun AlertBuilder<*>.customTitle(dsl: ViewManager.() -> Unit) {
|
||||
customTitle = ctx.UI(dsl).view
|
||||
}
|
||||
|
||||
fun AlertBuilder<*>.customView(dsl: ViewManager.() -> Unit) {
|
||||
customView = ctx.UI(dsl).view
|
||||
}
|
||||
|
||||
inline fun AlertBuilder<*>.okButton(noinline handler: (dialog: DialogInterface) -> Unit) =
|
||||
positiveButton(android.R.string.ok, handler)
|
||||
|
||||
inline fun AlertBuilder<*>.cancelButton(noinline handler: (dialog: DialogInterface) -> Unit) =
|
||||
negativeButton(android.R.string.cancel, handler)
|
||||
|
||||
inline fun AlertBuilder<*>.yesButton(noinline handler: (dialog: DialogInterface) -> Unit) =
|
||||
positiveButton(android.R.string.yes, handler)
|
||||
|
||||
inline fun AlertBuilder<*>.noButton(noinline handler: (dialog: DialogInterface) -> Unit) =
|
||||
negativeButton(android.R.string.no, handler)
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
//import android.app.AlertDialog
|
||||
//import android.content.Context
|
||||
//import android.content.DialogInterface
|
||||
//import android.database.Cursor
|
||||
//import android.graphics.drawable.Drawable
|
||||
//import android.view.KeyEvent
|
||||
//import android.view.View
|
||||
//import android.view.ViewManager
|
||||
//import android.widget.ListAdapter
|
||||
//
|
||||
//@Deprecated("Use AlertBuilder class instead.")
|
||||
//class AlertDialogBuilder(val ctx: Context) {
|
||||
// private var builder: AlertDialog.Builder? = AlertDialog.Builder(ctx)
|
||||
//
|
||||
// /**
|
||||
// * Returns the [AlertDialog] instance if created.
|
||||
// * Returns null until the [show] function is called.
|
||||
// */
|
||||
// var dialog: AlertDialog? = null
|
||||
// private set
|
||||
//
|
||||
// constructor(ankoContext: AnkoContext<*>) : this(ankoContext.ctx)
|
||||
//
|
||||
// fun dismiss() {
|
||||
// dialog?.dismiss()
|
||||
// }
|
||||
//
|
||||
// private fun checkBuilder() {
|
||||
// if (builder == null) {
|
||||
// throw IllegalStateException("show() was already called for this AlertDialogBuilder")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create the [AlertDialog] and display it on screen.
|
||||
// *
|
||||
// */
|
||||
// fun show(): AlertDialogBuilder {
|
||||
// checkBuilder()
|
||||
// dialog = builder!!.create()
|
||||
// builder = null
|
||||
// dialog!!.show()
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the [title] displayed in the dialog.
|
||||
// */
|
||||
// fun title(title: CharSequence) {
|
||||
// checkBuilder()
|
||||
// builder!!.setTitle(title)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the title using the given [title] resource id.
|
||||
// */
|
||||
// fun title(title: Int) {
|
||||
// checkBuilder()
|
||||
// builder!!.setTitle(title)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the [message] to display.
|
||||
// */
|
||||
// fun message(message: CharSequence) {
|
||||
// checkBuilder()
|
||||
// builder!!.setMessage(message)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the message to display using the given [message] resource id.
|
||||
// */
|
||||
// fun message(message: Int) {
|
||||
// checkBuilder()
|
||||
// builder!!.setMessage(message)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the resource id of the [Drawable] to be used in the title.
|
||||
// */
|
||||
// fun icon(icon: Int) {
|
||||
// checkBuilder()
|
||||
// builder!!.setIcon(icon)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the [icon] Drawable to be used in the title.
|
||||
// */
|
||||
// fun icon(icon: Drawable) {
|
||||
// checkBuilder()
|
||||
// builder!!.setIcon(icon)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the title using the custom [view].
|
||||
// */
|
||||
// fun customTitle(view: View) {
|
||||
// checkBuilder()
|
||||
// builder!!.setCustomTitle(view)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set the title using the custom DSL view.
|
||||
// */
|
||||
// fun customTitle(dsl: ViewManager.() -> Unit) {
|
||||
// checkBuilder()
|
||||
// val view = ctx.UI(dsl).view
|
||||
// builder!!.setCustomTitle(view)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a custom [view] to be the contents of the Dialog.
|
||||
// */
|
||||
// fun customView(view: View) {
|
||||
// checkBuilder()
|
||||
// builder!!.setView(view)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a custom DSL view to be the contents of the Dialog.
|
||||
// */
|
||||
// fun customView(dsl: ViewManager.() -> Unit) {
|
||||
// checkBuilder()
|
||||
// val view = ctx.UI(dsl).view
|
||||
// builder!!.setView(view)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set if the dialog is cancellable.
|
||||
// *
|
||||
// * @param cancellable if true, the created dialog will be cancellable.
|
||||
// */
|
||||
// fun cancellable(cancellable: Boolean = true) {
|
||||
// checkBuilder()
|
||||
// builder!!.setCancelable(cancellable)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Sets the [callback] that will be called if the dialog is canceled.
|
||||
// */
|
||||
// fun onCancel(callback: () -> Unit) {
|
||||
// checkBuilder()
|
||||
// builder!!.setOnCancelListener { callback() }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Sets the [callback] that will be called if a key is dispatched to the dialog.
|
||||
// */
|
||||
// fun onKey(callback: (keyCode: Int, e: KeyEvent) -> Boolean) {
|
||||
// checkBuilder()
|
||||
// builder!!.setOnKeyListener({ dialog, keyCode, event -> callback(keyCode, event) })
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the neutral button of the dialog is pressed.
|
||||
// *
|
||||
// * @param neutralText the text resource to display in the neutral button.
|
||||
// * @param callback the callback that will be called if the neutral button is pressed.
|
||||
// */
|
||||
// fun neutralButton(
|
||||
// neutralText: Int = android.R.string.ok,
|
||||
// callback: DialogInterface.() -> Unit = { dismiss() },
|
||||
// ) {
|
||||
// neutralButton(ctx.getString(neutralText), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the neutral button of the dialog is pressed.
|
||||
// *
|
||||
// * @param neutralText the text to display in the neutral button.
|
||||
// * @param callback the callback that will be called if the neutral button is pressed.
|
||||
// */
|
||||
// fun neutralButton(
|
||||
// neutralText: CharSequence,
|
||||
// callback: DialogInterface.() -> Unit = { dismiss() },
|
||||
// ) {
|
||||
// checkBuilder()
|
||||
// builder!!.setNeutralButton(neutralText, { dialog, which -> dialog.callback() })
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the positive button of the dialog is pressed.
|
||||
// *
|
||||
// * @param positiveText the text to display in the positive button.
|
||||
// * @param callback the callback that will be called if the positive button is pressed.
|
||||
// */
|
||||
// fun positiveButton(positiveText: Int, callback: DialogInterface.() -> Unit) {
|
||||
// positiveButton(ctx.getString(positiveText), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the positive button of the dialog is pressed.
|
||||
// *
|
||||
// * @param callback the callback that will be called if the positive button is pressed.
|
||||
// */
|
||||
// fun okButton(callback: DialogInterface.() -> Unit) {
|
||||
// positiveButton(ctx.getString(android.R.string.ok), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the positive button of the dialog is pressed.
|
||||
// *
|
||||
// * @param callback the callback that will be called if the positive button is pressed.
|
||||
// */
|
||||
// fun yesButton(callback: DialogInterface.() -> Unit) {
|
||||
// positiveButton(ctx.getString(android.R.string.yes), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the positive button of the dialog is pressed.
|
||||
// *
|
||||
// * @param positiveText the text to display in the positive button.
|
||||
// * @param callback the callback that will be called if the positive button is pressed.
|
||||
// */
|
||||
// fun positiveButton(positiveText: CharSequence, callback: DialogInterface.() -> Unit) {
|
||||
// checkBuilder()
|
||||
// builder!!.setPositiveButton(positiveText, { dialog, which -> dialog.callback() })
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the negative button of the dialog is pressed.
|
||||
// *
|
||||
// * @param negativeText the text to display in the negative button.
|
||||
// * @param callback the callback that will be called if the negative button is pressed.
|
||||
// */
|
||||
// fun negativeButton(negativeText: Int, callback: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
// negativeButton(ctx.getString(negativeText), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the negative button of the dialog is pressed.
|
||||
// *
|
||||
// * @param callback the callback that will be called if the negative button is pressed.
|
||||
// */
|
||||
// fun cancelButton(callback: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
// negativeButton(ctx.getString(android.R.string.cancel), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the negative button of the dialog is pressed.
|
||||
// *
|
||||
// * @param callback the callback that will be called if the negative button is pressed.
|
||||
// */
|
||||
// fun noButton(callback: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
// negativeButton(ctx.getString(android.R.string.no), callback)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Set a listener to be invoked when the negative button of the dialog is pressed.
|
||||
// *
|
||||
// * @param negativeText the text to display in the negative button.
|
||||
// * @param callback the callback that will be called if the negative button is pressed.
|
||||
// */
|
||||
// fun negativeButton(
|
||||
// negativeText: CharSequence,
|
||||
// callback: DialogInterface.() -> Unit = { dismiss() },
|
||||
// ) {
|
||||
// checkBuilder()
|
||||
// builder!!.setNegativeButton(negativeText, { dialog, which -> dialog.callback() })
|
||||
// }
|
||||
//
|
||||
// fun items(itemsId: Int, callback: (which: Int) -> Unit) {
|
||||
// items(ctx.resources!!.getTextArray(itemsId), callback)
|
||||
// }
|
||||
//
|
||||
// fun items(items: List<CharSequence>, callback: (which: Int) -> Unit) {
|
||||
// items(items.toTypedArray(), callback)
|
||||
// }
|
||||
//
|
||||
// fun items(items: Array<CharSequence>, callback: (which: Int) -> Unit) {
|
||||
// checkBuilder()
|
||||
// builder!!.setItems(items, { dialog, which -> callback(which) })
|
||||
// }
|
||||
//
|
||||
// fun adapter(adapter: ListAdapter, callback: (which: Int) -> Unit) {
|
||||
// checkBuilder()
|
||||
// builder!!.setAdapter(adapter, { dialog, which -> callback(which) })
|
||||
// }
|
||||
//
|
||||
// fun adapter(cursor: Cursor, labelColumn: String, callback: (which: Int) -> Unit) {
|
||||
// checkBuilder()
|
||||
// builder!!.setCursor(cursor, { dialog, which -> callback(which) }, labelColumn)
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.anko
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
val Android: AlertBuilderFactory<AlertDialog> = ::AndroidAlertBuilder
|
||||
|
||||
internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<AlertDialog> {
|
||||
private val builder = AlertDialog.Builder(ctx)
|
||||
|
||||
override var title: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setTitle(value) }
|
||||
|
||||
override var titleResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setTitle(value) }
|
||||
|
||||
override var message: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setMessage(value) }
|
||||
|
||||
override var messageResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setMessage(value) }
|
||||
|
||||
override var icon: Drawable
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setIcon(value) }
|
||||
|
||||
override var iconResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setIcon(value) }
|
||||
|
||||
override var customTitle: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setCustomTitle(value) }
|
||||
|
||||
override var customView: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setView(value) }
|
||||
|
||||
override var isCancelable: Boolean
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) { builder.setCancelable(value) }
|
||||
|
||||
override fun onCancelled(handler: (DialogInterface) -> Unit) {
|
||||
builder.setOnCancelListener(handler)
|
||||
}
|
||||
|
||||
override fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) {
|
||||
builder.setOnKeyListener(handler)
|
||||
}
|
||||
|
||||
override fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setPositiveButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun positiveButton(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setPositiveButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNegativeButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun negativeButton(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNegativeButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNeutralButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun neutralPressed(buttonTextResource: Int, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNeutralButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun items(items: List<CharSequence>, onItemSelected: (dialog: DialogInterface, index: Int) -> Unit) {
|
||||
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
|
||||
onItemSelected(dialog, which)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> items(items: List<T>, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) {
|
||||
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
|
||||
onItemSelected(dialog, items[which], which)
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(): AlertDialog = builder.create()
|
||||
|
||||
override fun show(): AlertDialog = builder.show()
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
inline fun AnkoContext<*>.alert(
|
||||
message: CharSequence,
|
||||
title: CharSequence? = null,
|
||||
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
) = ctx.alert(message, title, init)
|
||||
|
||||
inline fun Fragment.alert(
|
||||
message: CharSequence,
|
||||
title: CharSequence? = null,
|
||||
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
) = requireActivity().alert(message, title, init)
|
||||
|
||||
fun Context.alert(
|
||||
message: CharSequence,
|
||||
title: CharSequence? = null,
|
||||
init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
): AlertBuilder<AlertDialog> {
|
||||
return AndroidAlertBuilder(this).apply {
|
||||
if (title != null) {
|
||||
this.title = title
|
||||
}
|
||||
this.message = message
|
||||
if (init != null) init()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.alert(
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
) = ctx.alert(message, title, init)
|
||||
|
||||
inline fun Fragment.alert(
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
) = requireActivity().alert(message, title, init)
|
||||
|
||||
fun Context.alert(
|
||||
messageResource: Int,
|
||||
titleResource: Int? = null,
|
||||
init: (AlertBuilder<DialogInterface>.() -> Unit)? = null,
|
||||
): AlertBuilder<DialogInterface> {
|
||||
return AndroidAlertBuilder(this).apply {
|
||||
if (titleResource != null) {
|
||||
this.titleResource = titleResource
|
||||
}
|
||||
this.messageResource = messageResource
|
||||
if (init != null) init()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.alert(noinline init: AlertBuilder<DialogInterface>.() -> Unit) =
|
||||
ctx.alert(init)
|
||||
|
||||
inline fun Fragment.alert(noinline init: AlertBuilder<DialogInterface>.() -> Unit) =
|
||||
requireActivity().alert(init)
|
||||
|
||||
fun Context.alert(init: AlertBuilder<DialogInterface>.() -> Unit): AlertBuilder<DialogInterface> =
|
||||
AndroidAlertBuilder(this).apply { init() }
|
||||
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun AnkoContext<*>.progressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = ctx.progressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun Fragment.progressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = requireActivity().progressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//fun Context.progressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = progressDialog(false, message?.let { getString(it) }, title?.let { getString(it) }, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun AnkoContext<*>.indeterminateProgressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = ctx.indeterminateProgressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun Fragment.indeterminateProgressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = requireActivity().indeterminateProgressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//fun Context.indeterminateProgressDialog(
|
||||
// message: Int? = null,
|
||||
// title: Int? = null,
|
||||
// init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = progressDialog(true, message?.let { getString(it) }, title?.let { getString(it) }, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun AnkoContext<*>.progressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = ctx.progressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun Fragment.progressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = requireActivity().progressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//fun Context.progressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = progressDialog(false, message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun AnkoContext<*>.indeterminateProgressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = ctx.indeterminateProgressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//inline fun Fragment.indeterminateProgressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// noinline init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = requireActivity().indeterminateProgressDialog(message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//fun Context.indeterminateProgressDialog(
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = progressDialog(true, message, title, init)
|
||||
//
|
||||
//@Deprecated(message = "Android progress dialogs are deprecated")
|
||||
//private fun Context.progressDialog(
|
||||
// indeterminate: Boolean,
|
||||
// message: CharSequence? = null,
|
||||
// title: CharSequence? = null,
|
||||
// init: (ProgressDialog.() -> Unit)? = null,
|
||||
//) = ProgressDialog(this).apply {
|
||||
// isIndeterminate = indeterminate
|
||||
// if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
// if (message != null) setMessage(message)
|
||||
// if (title != null) setTitle(title)
|
||||
// if (init != null) init()
|
||||
// show()
|
||||
//}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
inline fun AnkoContext<*>.selector(
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
noinline onClick: (DialogInterface, Int) -> Unit,
|
||||
) = ctx.selector(title, items, onClick)
|
||||
|
||||
inline fun Fragment.selector(
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
noinline onClick: (DialogInterface, Int) -> Unit,
|
||||
) = requireActivity().selector(title, items, onClick)
|
||||
|
||||
fun Context.selector(
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
onClick: (DialogInterface, Int) -> Unit,
|
||||
) {
|
||||
with(AndroidAlertBuilder(this)) {
|
||||
if (title != null) {
|
||||
this.title = title
|
||||
}
|
||||
items(items, onClick)
|
||||
show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.jetbrains.anko.internals.AnkoInternals.createAnkoContext
|
||||
|
||||
@DslMarker
|
||||
private annotation class AnkoContextDslMarker
|
||||
|
||||
@AnkoContextDslMarker
|
||||
interface AnkoContext<out T> : ViewManager {
|
||||
val ctx: Context
|
||||
val owner: T
|
||||
val view: View
|
||||
|
||||
override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun removeView(view: View) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context> =
|
||||
AnkoContextImpl(ctx, ctx, setContentView)
|
||||
|
||||
fun createReusable(ctx: Context, setContentView: Boolean = false): AnkoContext<Context> =
|
||||
ReusableAnkoContext(ctx, ctx, setContentView)
|
||||
|
||||
fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T> =
|
||||
AnkoContextImpl(ctx, owner, setContentView)
|
||||
|
||||
fun <T> createReusable(
|
||||
ctx: Context,
|
||||
owner: T,
|
||||
setContentView: Boolean = false,
|
||||
): AnkoContext<T> = ReusableAnkoContext(ctx, owner, setContentView)
|
||||
|
||||
fun <T : ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
|
||||
}
|
||||
}
|
||||
|
||||
internal class DelegatingAnkoContext<T : ViewGroup>(override val owner: T) : AnkoContext<T> {
|
||||
override val ctx: Context = owner.context
|
||||
override val view: View = owner
|
||||
|
||||
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
|
||||
if (view == null) return
|
||||
|
||||
if (params == null) {
|
||||
owner.addView(view)
|
||||
} else {
|
||||
owner.addView(view, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ReusableAnkoContext<T>(
|
||||
override val ctx: Context,
|
||||
override val owner: T,
|
||||
setContentView: Boolean,
|
||||
) : AnkoContextImpl<T>(ctx, owner, setContentView) {
|
||||
override fun alreadyHasView() {}
|
||||
}
|
||||
|
||||
open class AnkoContextImpl<T>(
|
||||
override val ctx: Context,
|
||||
override val owner: T,
|
||||
private val setContentView: Boolean,
|
||||
) : AnkoContext<T> {
|
||||
private var myView: View? = null
|
||||
|
||||
override val view: View
|
||||
get() = myView ?: throw IllegalStateException("View was not set previously")
|
||||
|
||||
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
|
||||
if (view == null) return
|
||||
|
||||
if (myView != null) {
|
||||
alreadyHasView()
|
||||
}
|
||||
|
||||
this.myView = view
|
||||
|
||||
if (setContentView) {
|
||||
doAddView(ctx, view)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAddView(context: Context, view: View) {
|
||||
when (context) {
|
||||
is Activity -> context.setContentView(view)
|
||||
is ContextWrapper -> doAddView(context.baseContext, view)
|
||||
else -> throw IllegalStateException("Context is not an Activity, can't set content view")
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun alreadyHasView(): Unit =
|
||||
throw IllegalStateException("View is already set: $myView")
|
||||
}
|
||||
|
||||
@Suppress("FunctionNaming")
|
||||
inline fun Context.UI(
|
||||
setContentView: Boolean,
|
||||
init: AnkoContext<Context>.() -> Unit,
|
||||
): AnkoContext<Context> =
|
||||
createAnkoContext(this, init, setContentView)
|
||||
|
||||
@Suppress("FunctionNaming")
|
||||
inline fun Context.UI(init: AnkoContext<Context>.() -> Unit): AnkoContext<Context> =
|
||||
createAnkoContext(this, init)
|
||||
|
||||
@Suppress("FunctionNaming")
|
||||
inline fun Fragment.UI(init: AnkoContext<Fragment>.() -> Unit): AnkoContext<Fragment> =
|
||||
createAnkoContext(requireContext(), init)
|
||||
|
||||
interface AnkoComponent<in T> {
|
||||
fun createView(ui: AnkoContext<T>): View
|
||||
}
|
||||
|
||||
fun <T : Activity> AnkoComponent<T>.setContentView(activity: T): View =
|
||||
createView(AnkoContextImpl(activity, activity, true))
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.fragment.app.Fragment
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
* Execute [f] on the application UI thread.
|
||||
*/
|
||||
fun Context.runOnUiThread(f: Context.() -> Unit) {
|
||||
if (Looper.getMainLooper() === Looper.myLooper()) f() else ContextHelper.handler.post { f() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] on the application UI thread.
|
||||
*/
|
||||
inline fun Fragment.runOnUiThread(crossinline f: () -> Unit) {
|
||||
requireContext().runOnUiThread { f() }
|
||||
}
|
||||
|
||||
class AnkoAsyncContext<T>(val weakRef: WeakReference<T>)
|
||||
|
||||
/**
|
||||
* Execute [f] on the application UI thread.
|
||||
* If the [doAsync] receiver still exists (was not collected by GC),
|
||||
* [f] gets it as a parameter ([f] gets null if the receiver does not exist anymore).
|
||||
*/
|
||||
fun <T> AnkoAsyncContext<T>.onComplete(f: (T?) -> Unit) {
|
||||
val ref = weakRef.get()
|
||||
if (Looper.getMainLooper() === Looper.myLooper()) {
|
||||
f(ref)
|
||||
} else {
|
||||
ContextHelper.handler.post { f(ref) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] on the application UI thread.
|
||||
* [doAsync] receiver will be passed to [f].
|
||||
* If the receiver does not exist anymore (it was collected by GC), [f] will not be executed.
|
||||
*/
|
||||
fun <T> AnkoAsyncContext<T>.uiThread(f: (T) -> Unit): Boolean {
|
||||
val ref = weakRef.get() ?: return false
|
||||
if (Looper.getMainLooper() === Looper.myLooper()) {
|
||||
f(ref)
|
||||
} else {
|
||||
ContextHelper.handler.post { f(ref) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] on the application UI thread if the underlying [Activity] still exists and is not finished.
|
||||
* The receiver [Activity] will be passed to [f].
|
||||
* If it is not exist anymore or if it was finished, [f] will not be called.
|
||||
*/
|
||||
fun <T : Activity> AnkoAsyncContext<T>.activityUiThread(f: (T) -> Unit): Boolean {
|
||||
val activity = weakRef.get() ?: return false
|
||||
if (activity.isFinishing) return false
|
||||
activity.runOnUiThread { f(activity) }
|
||||
return true
|
||||
}
|
||||
|
||||
fun <T : Activity> AnkoAsyncContext<T>.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
|
||||
val activity = weakRef.get() ?: return false
|
||||
if (activity.isFinishing) return false
|
||||
activity.runOnUiThread { activity.f(activity) }
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmName("activityContextUiThread")
|
||||
fun <T : Activity> AnkoAsyncContext<AnkoContext<T>>.activityUiThread(f: (T) -> Unit): Boolean {
|
||||
val activity = weakRef.get()?.owner ?: return false
|
||||
if (activity.isFinishing) return false
|
||||
activity.runOnUiThread { f(activity) }
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmName("activityContextUiThreadWithContext")
|
||||
fun <T : Activity> AnkoAsyncContext<AnkoContext<T>>.activityUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
|
||||
val activity = weakRef.get()?.owner ?: return false
|
||||
if (activity.isFinishing) return false
|
||||
activity.runOnUiThread { activity.f(activity) }
|
||||
return true
|
||||
}
|
||||
|
||||
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
|
||||
fun <T : Fragment> AnkoAsyncContext<T>.fragmentUiThread(f: (T) -> Unit): Boolean {
|
||||
val fragment = weakRef.get() ?: return false
|
||||
if (fragment.isDetached) return false
|
||||
val activity = fragment.activity ?: return false
|
||||
activity.runOnUiThread { f(fragment) }
|
||||
return true
|
||||
}
|
||||
|
||||
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
|
||||
fun <T : Fragment> AnkoAsyncContext<T>.fragmentUiThreadWithContext(f: Context.(T) -> Unit): Boolean {
|
||||
val fragment = weakRef.get() ?: return false
|
||||
if (fragment.isDetached) return false
|
||||
val activity = fragment.activity ?: return false
|
||||
activity.runOnUiThread { activity.f(fragment) }
|
||||
return true
|
||||
}
|
||||
|
||||
private val crashLogger = { throwable: Throwable -> throwable.printStackTrace() }
|
||||
|
||||
/**
|
||||
* Execute [task] asynchronously.
|
||||
*
|
||||
* @param exceptionHandler optional exception handler.
|
||||
* If defined, any exceptions thrown inside [task] will be passed to it. If not, exceptions will be ignored.
|
||||
* @param task the code to execute asynchronously.
|
||||
*/
|
||||
fun <T> T.doAsync(
|
||||
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
|
||||
task: AnkoAsyncContext<T>.() -> Unit,
|
||||
): Future<Unit> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return BackgroundExecutor.submit {
|
||||
return@submit try {
|
||||
context.task()
|
||||
} catch (thr: Throwable) {
|
||||
val result = exceptionHandler?.invoke(thr)
|
||||
result ?: Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> T.doAsync(
|
||||
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
|
||||
executorService: ExecutorService,
|
||||
task: AnkoAsyncContext<T>.() -> Unit,
|
||||
): Future<Unit> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return executorService.submit<Unit> {
|
||||
try {
|
||||
context.task()
|
||||
} catch (thr: Throwable) {
|
||||
exceptionHandler?.invoke(thr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, R> T.doAsyncResult(
|
||||
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
|
||||
task: AnkoAsyncContext<T>.() -> R,
|
||||
): Future<R> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return BackgroundExecutor.submit {
|
||||
try {
|
||||
context.task()
|
||||
} catch (thr: Throwable) {
|
||||
exceptionHandler?.invoke(thr)
|
||||
throw thr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, R> T.doAsyncResult(
|
||||
exceptionHandler: ((Throwable) -> Unit)? = crashLogger,
|
||||
executorService: ExecutorService,
|
||||
task: AnkoAsyncContext<T>.() -> R,
|
||||
): Future<R> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return executorService.submit<R> {
|
||||
try {
|
||||
context.task()
|
||||
} catch (thr: Throwable) {
|
||||
exceptionHandler?.invoke(thr)
|
||||
throw thr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object BackgroundExecutor {
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var executor: ExecutorService =
|
||||
Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors())
|
||||
|
||||
fun <T> submit(task: () -> T): Future<T> = executor.submit(task)
|
||||
}
|
||||
|
||||
private object ContextHelper {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
inline val AnkoContext<*>.resources: Resources
|
||||
get() = ctx.resources
|
||||
|
||||
inline val AnkoContext<*>.assets: AssetManager
|
||||
get() = ctx.assets
|
||||
|
||||
inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
|
||||
inline val Context.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
inline val Fragment.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
inline val Fragment.act: Activity?
|
||||
get() = activity
|
||||
|
||||
inline val Fragment.ctx: Context?
|
||||
get() = context
|
||||
|
||||
@Deprecated(message = "Inline", replaceWith = ReplaceWith("this"))
|
||||
inline val Context.ctx: Context
|
||||
get() = this
|
||||
|
||||
@Deprecated(message = "Inline", replaceWith = ReplaceWith("this"))
|
||||
inline val Activity.act: Activity
|
||||
get() = this
|
||||
|
||||
/**
|
||||
* Returns the content view of this Activity if set, null otherwise.
|
||||
*/
|
||||
inline val Activity.contentView: View?
|
||||
get() = findOptional<ViewGroup>(android.R.id.content)?.getChildAt(0)
|
||||
|
||||
inline fun <reified T : View> View.find(@IdRes id: Int): T = findViewById(id)
|
||||
inline fun <reified T : View> Activity.find(@IdRes id: Int): T = findViewById(id)
|
||||
|
||||
inline fun <reified T : View> Fragment.find(@IdRes id: Int): T = view?.findViewById(id) as T
|
||||
inline fun <reified T : View> Dialog.find(@IdRes id: Int): T = findViewById(id)
|
||||
|
||||
inline fun <reified T : View> View.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
|
||||
inline fun <reified T : View> Activity.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
|
||||
|
||||
inline fun <reified T : View> Fragment.findOptional(@IdRes id: Int): T? =
|
||||
view?.findViewById(id) as? T
|
||||
|
||||
inline fun <reified T : View> Dialog.findOptional(@IdRes id: Int): T? = findViewById(id) as? T
|
||||
|
||||
//@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
|
||||
//inline fun <T : Fragment> T.withArguments(vararg params: Pair<String, Any?>): T {
|
||||
// arguments = bundleOf(*params)
|
||||
// return this
|
||||
//}
|
||||
|
||||
//@Deprecated(
|
||||
// message = "Use the Android KTX version",
|
||||
// replaceWith = ReplaceWith("bundleOf(*params)", "androidx.core.os.bundleOf")
|
||||
//)
|
||||
//fun bundleOf(vararg params: Pair<String, Any?>): Bundle {
|
||||
// val b = Bundle()
|
||||
// for (p in params) {
|
||||
// val (k, v) = p
|
||||
// when (v) {
|
||||
// null -> b.putSerializable(k, null)
|
||||
// is Boolean -> b.putBoolean(k, v)
|
||||
// is Byte -> b.putByte(k, v)
|
||||
// is Char -> b.putChar(k, v)
|
||||
// is Short -> b.putShort(k, v)
|
||||
// is Int -> b.putInt(k, v)
|
||||
// is Long -> b.putLong(k, v)
|
||||
// is Float -> b.putFloat(k, v)
|
||||
// is Double -> b.putDouble(k, v)
|
||||
// is String -> b.putString(k, v)
|
||||
// is CharSequence -> b.putCharSequence(k, v)
|
||||
// is Parcelable -> b.putParcelable(k, v)
|
||||
// is Serializable -> b.putSerializable(k, v)
|
||||
// is BooleanArray -> b.putBooleanArray(k, v)
|
||||
// is ByteArray -> b.putByteArray(k, v)
|
||||
// is CharArray -> b.putCharArray(k, v)
|
||||
// is DoubleArray -> b.putDoubleArray(k, v)
|
||||
// is FloatArray -> b.putFloatArray(k, v)
|
||||
// is IntArray -> b.putIntArray(k, v)
|
||||
// is LongArray -> b.putLongArray(k, v)
|
||||
// is Array<*> -> {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// when {
|
||||
// v.isArrayOf<Parcelable>() -> b.putParcelableArray(k, v as Array<out Parcelable>)
|
||||
// v.isArrayOf<CharSequence>() -> b.putCharSequenceArray(
|
||||
// k,
|
||||
// v as Array<out CharSequence>
|
||||
// )
|
||||
// v.isArrayOf<String>() -> b.putStringArray(k, v as Array<out String>)
|
||||
// else -> throw AnkoException("Unsupported bundle component (${v.javaClass})")
|
||||
// }
|
||||
// }
|
||||
// is ShortArray -> b.putShortArray(k, v)
|
||||
// is Bundle -> b.putBundle(k, v)
|
||||
// else -> throw AnkoException("Unsupported bundle component (${v.javaClass})")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return b
|
||||
//}
|
||||
|
||||
inline val Context.displayMetrics: android.util.DisplayMetrics
|
||||
get() = resources.displayMetrics
|
||||
|
||||
inline val Context.configuration: Configuration
|
||||
get() = resources.configuration
|
||||
|
||||
inline val AnkoContext<*>.displayMetrics: android.util.DisplayMetrics
|
||||
get() = ctx.resources.displayMetrics
|
||||
|
||||
inline val AnkoContext<*>.configuration: Configuration
|
||||
get() = ctx.resources.configuration
|
||||
|
||||
inline val Configuration.portrait: Boolean
|
||||
get() = orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
||||
inline val Configuration.landscape: Boolean
|
||||
get() = orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
inline val Configuration.long: Boolean
|
||||
get() = (screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused", "TopLevelPropertyNaming")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
|
||||
import org.jetbrains.anko.internals.AnkoInternals.noGetter
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
const val matchParent: Int = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
const val wrapContent: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
|
||||
var ViewGroup.MarginLayoutParams.verticalMargin: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(v) {
|
||||
topMargin = v
|
||||
bottomMargin = v
|
||||
}
|
||||
|
||||
var ViewGroup.MarginLayoutParams.horizontalMargin: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(v) {
|
||||
leftMargin = v
|
||||
rightMargin = v
|
||||
}
|
||||
|
||||
var ViewGroup.MarginLayoutParams.margin: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(v) {
|
||||
leftMargin = v
|
||||
rightMargin = v
|
||||
topMargin = v
|
||||
bottomMargin = v
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
|
||||
//inline val Context.vibrator: android.os.Vibrator
|
||||
// get() = getSystemService(Context.VIBRATOR_SERVICE) as android.os.Vibrator
|
||||
|
||||
inline val Context.layoutInflater: android.view.LayoutInflater
|
||||
get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as android.view.LayoutInflater
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
|
||||
import org.jetbrains.anko.internals.AnkoInternals.noGetter
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
var View.backgroundDrawable: Drawable?
|
||||
inline get() = background
|
||||
set(value) {
|
||||
background = value
|
||||
}
|
||||
|
||||
var View.backgroundColorResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(colorId) = setBackgroundColor(ContextCompat.getColor(context, colorId))
|
||||
|
||||
var View.leftPadding: Int
|
||||
inline get() = paddingLeft
|
||||
set(value) = setPadding(value, paddingTop, paddingRight, paddingBottom)
|
||||
|
||||
var View.topPadding: Int
|
||||
inline get() = paddingTop
|
||||
set(value) = setPadding(paddingLeft, value, paddingRight, paddingBottom)
|
||||
|
||||
var View.rightPadding: Int
|
||||
inline get() = paddingRight
|
||||
set(value) = setPadding(paddingLeft, paddingTop, value, paddingBottom)
|
||||
|
||||
var View.bottomPadding: Int
|
||||
inline get() = paddingBottom
|
||||
set(value) = setPadding(paddingLeft, paddingTop, paddingRight, value)
|
||||
|
||||
@Deprecated("Use horizontalPadding instead", ReplaceWith("horizontalPadding"))
|
||||
var View.paddingHorizontal: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setPadding(value, paddingTop, value, paddingBottom)
|
||||
|
||||
var View.horizontalPadding: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setPadding(value, paddingTop, value, paddingBottom)
|
||||
|
||||
@Deprecated("Use verticalPadding instead", ReplaceWith("verticalPadding"))
|
||||
var View.paddingVertical: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setPadding(paddingLeft, value, paddingRight, value)
|
||||
|
||||
var View.verticalPadding: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setPadding(paddingLeft, value, paddingRight, value)
|
||||
|
||||
var View.padding: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
inline set(value) = setPadding(value, value, value, value)
|
||||
|
||||
var TextView.allCaps: Boolean
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
inline set(value) {
|
||||
isAllCaps = value
|
||||
}
|
||||
|
||||
var TextView.ems: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
inline set(value) = setEms(value)
|
||||
|
||||
inline var TextView.isSelectable: Boolean
|
||||
get() = isTextSelectable
|
||||
set(value) = setTextIsSelectable(value)
|
||||
|
||||
var TextView.textAppearance: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setTextAppearance(value)
|
||||
|
||||
var TextView.textSizeDimen: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(value) = setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(value))
|
||||
|
||||
var TextView.textColorResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = noGetter()
|
||||
set(colorId) = setTextColor(ContextCompat.getColor(context, colorId))
|
||||
|
||||
var ImageView.image: Drawable?
|
||||
inline get() = drawable
|
||||
inline set(value) = setImageDrawable(value)
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused", "MatchingDeclarationName", "ClassNaming")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import org.jetbrains.anko.custom.ankoView
|
||||
|
||||
@PublishedApi
|
||||
internal object `$$Anko$Factories$CustomViews` {
|
||||
val VERTICAL_LAYOUT_FACTORY = { ctx: Context ->
|
||||
val view = _LinearLayout(ctx)
|
||||
view.orientation = LinearLayout.VERTICAL
|
||||
view
|
||||
}
|
||||
|
||||
val EDIT_TEXT = { ctx: Context -> EditText(ctx) }
|
||||
|
||||
val HORIZONTAL_PROGRESS_BAR_FACTORY = { ctx: Context ->
|
||||
ProgressBar(ctx, null, android.R.attr.progressBarStyleHorizontal)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun ViewManager.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
|
||||
inline fun ViewManager.verticalLayout(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
|
||||
): LinearLayout {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun Context.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
|
||||
inline fun Context.verticalLayout(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
|
||||
): LinearLayout {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun Activity.verticalLayout(theme: Int = 0): LinearLayout = verticalLayout(theme) {}
|
||||
inline fun Activity.verticalLayout(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker _LinearLayout).() -> Unit,
|
||||
): LinearLayout {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun ViewManager.editText(constraints: InputConstraints, theme: Int = 0): EditText =
|
||||
editText(constraints, theme) {}
|
||||
|
||||
inline fun ViewManager.editText(
|
||||
constraints: InputConstraints,
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker EditText).() -> Unit,
|
||||
): EditText {
|
||||
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
|
||||
v.inputType = constraints.value
|
||||
return v
|
||||
}
|
||||
|
||||
inline fun Context.editText(constraints: InputConstraints, theme: Int = 0): EditText =
|
||||
editText(constraints, theme) {}
|
||||
|
||||
inline fun Context.editText(
|
||||
constraints: InputConstraints,
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker EditText).() -> Unit,
|
||||
): EditText {
|
||||
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
|
||||
v.inputType = constraints.value
|
||||
return v
|
||||
}
|
||||
|
||||
inline fun Activity.editText(constraints: InputConstraints, theme: Int = 0): EditText =
|
||||
editText(constraints, theme) {}
|
||||
|
||||
inline fun Activity.editText(
|
||||
constraints: InputConstraints,
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker EditText).() -> Unit,
|
||||
): EditText {
|
||||
val v = ankoView(`$$Anko$Factories$CustomViews`.EDIT_TEXT, theme, init)
|
||||
v.inputType = constraints.value
|
||||
return v
|
||||
}
|
||||
|
||||
inline fun ViewManager.horizontalProgressBar(theme: Int = 0): ProgressBar =
|
||||
horizontalProgressBar(theme) {}
|
||||
|
||||
inline fun ViewManager.horizontalProgressBar(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
|
||||
): ProgressBar {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun Context.horizontalProgressBar(theme: Int = 0): ProgressBar =
|
||||
horizontalProgressBar(theme) {}
|
||||
|
||||
inline fun Context.horizontalProgressBar(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
|
||||
): ProgressBar {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun Activity.horizontalProgressBar(theme: Int = 0): ProgressBar =
|
||||
horizontalProgressBar(theme) {}
|
||||
|
||||
inline fun Activity.horizontalProgressBar(
|
||||
theme: Int = 0,
|
||||
init: (@AnkoViewDslMarker ProgressBar).() -> Unit,
|
||||
): ProgressBar {
|
||||
return ankoView(`$$Anko$Factories$CustomViews`.HORIZONTAL_PROGRESS_BAR_FACTORY, theme, init)
|
||||
}
|
||||
|
||||
inline fun <T : View> ViewManager.include(layoutId: Int): T = include(layoutId, {})
|
||||
inline fun <T : View> ViewManager.include(
|
||||
layoutId: Int,
|
||||
init: (@AnkoViewDslMarker T).() -> Unit,
|
||||
): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
|
||||
}
|
||||
|
||||
inline fun <T : View> ViewGroup.include(layoutId: Int): T = include(layoutId, {})
|
||||
inline fun <T : View> ViewGroup.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, this, false) as T }, 0) { init() }
|
||||
}
|
||||
|
||||
inline fun <T : View> Context.include(layoutId: Int): T = include(layoutId, {})
|
||||
inline fun <T : View> Context.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
|
||||
}
|
||||
|
||||
inline fun <T : View> Activity.include(layoutId: Int): T = include(layoutId, {})
|
||||
inline fun <T : View> Activity.include(layoutId: Int, init: (@AnkoViewDslMarker T).() -> Unit): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }, 0) { init() }
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
typealias AlertBuilderFactory<D> = (Context) -> AlertBuilder<D>
|
||||
|
||||
inline fun <D : DialogInterface> AnkoContext<*>.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
message: String,
|
||||
title: String? = null,
|
||||
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
) = ctx.alert(factory, message, title, init)
|
||||
|
||||
inline fun <D : DialogInterface> Fragment.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
message: String,
|
||||
title: String? = null,
|
||||
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
) = requireActivity().alert(factory, message, title, init)
|
||||
|
||||
fun <D : DialogInterface> Context.alert(
|
||||
factory: AlertBuilderFactory<D>,
|
||||
message: String,
|
||||
title: String? = null,
|
||||
init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
): AlertBuilder<D> {
|
||||
return factory(this).apply {
|
||||
if (title != null) {
|
||||
this.title = title
|
||||
}
|
||||
this.message = message
|
||||
if (init != null) init()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <D : DialogInterface> AnkoContext<*>.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
) = ctx.alert(factory, message, title, init)
|
||||
|
||||
inline fun <D : DialogInterface> Fragment.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
noinline init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
) = requireActivity().alert(factory, message, title, init)
|
||||
|
||||
fun <D : DialogInterface> Context.alert(
|
||||
factory: AlertBuilderFactory<D>,
|
||||
messageResource: Int,
|
||||
titleResource: Int? = null,
|
||||
init: (AlertBuilder<D>.() -> Unit)? = null,
|
||||
): AlertBuilder<D> {
|
||||
return factory(this).apply {
|
||||
if (titleResource != null) {
|
||||
this.titleResource = titleResource
|
||||
}
|
||||
this.messageResource = messageResource
|
||||
if (init != null) init()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <D : DialogInterface> AnkoContext<*>.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
noinline init: AlertBuilder<D>.() -> Unit,
|
||||
) = ctx.alert(factory, init)
|
||||
|
||||
inline fun <D : DialogInterface> Fragment.alert(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
noinline init: AlertBuilder<D>.() -> Unit,
|
||||
) = requireActivity().alert(factory, init)
|
||||
|
||||
fun <D : DialogInterface> Context.alert(
|
||||
factory: AlertBuilderFactory<D>,
|
||||
init: AlertBuilder<D>.() -> Unit,
|
||||
): AlertBuilder<D> = factory(this).apply { init() }
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
const val LDPI: Int = DisplayMetrics.DENSITY_LOW
|
||||
const val MDPI: Int = DisplayMetrics.DENSITY_MEDIUM
|
||||
const val HDPI: Int = DisplayMetrics.DENSITY_HIGH
|
||||
|
||||
const val TVDPI: Int = DisplayMetrics.DENSITY_TV
|
||||
const val XHDPI: Int = DisplayMetrics.DENSITY_XHIGH
|
||||
const val XXHDPI: Int = DisplayMetrics.DENSITY_XXHIGH
|
||||
const val XXXHDPI: Int = DisplayMetrics.DENSITY_XXXHIGH
|
||||
|
||||
const val MAXDPI: Int = 0xfffe
|
||||
|
||||
//returns dip(dp) dimension value in pixels
|
||||
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
|
||||
fun Context.dip(value: Float): Int = (value * resources.displayMetrics.density).toInt()
|
||||
|
||||
//return sp dimension value in pixels
|
||||
fun Context.sp(value: Int): Int = (value * resources.displayMetrics.scaledDensity).toInt()
|
||||
fun Context.sp(value: Float): Int = (value * resources.displayMetrics.scaledDensity).toInt()
|
||||
|
||||
//converts px value into dip or sp
|
||||
fun Context.px2dip(px: Int): Float = px.toFloat() / resources.displayMetrics.density
|
||||
fun Context.px2sp(px: Int): Float = px.toFloat() / resources.displayMetrics.scaledDensity
|
||||
|
||||
fun Context.dimen(@DimenRes resource: Int): Int = resources.getDimensionPixelSize(resource)
|
||||
|
||||
//the same for nested DSL components
|
||||
inline fun AnkoContext<*>.dip(value: Int): Int = ctx.dip(value)
|
||||
inline fun AnkoContext<*>.dip(value: Float): Int = ctx.dip(value)
|
||||
inline fun AnkoContext<*>.sp(value: Int): Int = ctx.sp(value)
|
||||
inline fun AnkoContext<*>.sp(value: Float): Int = ctx.sp(value)
|
||||
inline fun AnkoContext<*>.px2dip(px: Int): Float = ctx.px2dip(px)
|
||||
inline fun AnkoContext<*>.px2sp(px: Int): Float = ctx.px2sp(px)
|
||||
inline fun AnkoContext<*>.dimen(@DimenRes resource: Int): Int = ctx.dimen(resource)
|
||||
|
||||
//the same for the views
|
||||
inline fun View.dip(value: Int): Int = context.dip(value)
|
||||
inline fun View.dip(value: Float): Int = context.dip(value)
|
||||
inline fun View.sp(value: Int): Int = context.sp(value)
|
||||
inline fun View.sp(value: Float): Int = context.sp(value)
|
||||
inline fun View.px2dip(px: Int): Float = context.px2dip(px)
|
||||
inline fun View.px2sp(px: Int): Float = context.px2sp(px)
|
||||
inline fun View.dimen(@DimenRes resource: Int): Int = context.dimen(resource)
|
||||
|
||||
//the same for Fragments
|
||||
inline fun Fragment.dip(value: Int): Int = requireContext().dip(value)
|
||||
inline fun Fragment.dip(value: Float): Int = requireContext().dip(value)
|
||||
inline fun Fragment.sp(value: Int): Int = requireContext().sp(value)
|
||||
inline fun Fragment.sp(value: Float): Int = requireContext().sp(value)
|
||||
inline fun Fragment.px2dip(px: Int): Float = requireContext().px2dip(px)
|
||||
inline fun Fragment.px2sp(px: Int): Float = requireContext().px2sp(px)
|
||||
inline fun Fragment.dimen(@DimenRes resource: Int): Int = requireContext().dimen(resource)
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
|
||||
open class AnkoException(message: String = "") : RuntimeException(message)
|
||||
|
||||
/**
|
||||
* Return the grayscale color with the zero opacity using the single color value.
|
||||
* E.g., 0xC0 will be translated to 0xC0C0C0.
|
||||
*/
|
||||
val Int.gray: Int
|
||||
get() = this or (this shl 8) or (this shl 16)
|
||||
|
||||
/**
|
||||
* Return the color with 0xFF opacity.
|
||||
* E.g., 0xabcdef will be translated to 0xFFabcdef.
|
||||
*/
|
||||
val Int.opaque: Int
|
||||
get() = this or 0xff000000.toInt()
|
||||
|
||||
/**
|
||||
* Return the color with the given alpha value.
|
||||
* Examples:
|
||||
* 0xabcdef.withAlpha(0xCF) == 0xCFabcdef
|
||||
* 0xFFabcdef.withAlpha(0xCF) == 0xCFabcdef
|
||||
*
|
||||
* @param alpha the alpha channel value: [0x0..0xFF].
|
||||
* @return the color with the given alpha value applied.
|
||||
*/
|
||||
fun Int.withAlpha(alpha: Int): Int {
|
||||
require(alpha in 0..0xFF)
|
||||
return this and 0x00FFFFFF or (alpha shl 24)
|
||||
}
|
||||
|
||||
enum class ScreenSize {
|
||||
SMALL,
|
||||
NORMAL,
|
||||
LARGE,
|
||||
XLARGE
|
||||
}
|
||||
|
||||
enum class UiMode {
|
||||
NORMAL,
|
||||
CAR,
|
||||
DESK,
|
||||
TELEVISION,
|
||||
APPLIANCE,
|
||||
WATCH
|
||||
}
|
||||
|
||||
enum class Orientation {
|
||||
PORTRAIT,
|
||||
LANDSCAPE,
|
||||
SQUARE
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] if the device configuration matches all given constraints.
|
||||
* You can use named arguments to provide only the relevant constraints.
|
||||
* All null constraints are ignored.
|
||||
*
|
||||
* @param screenSize the required screen size.
|
||||
* @param density the required screen density.
|
||||
* @param language the required system language.
|
||||
* @param orientation the current screen orientation.
|
||||
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
|
||||
* @param fromSdk the minimal SDK version for code to execute.
|
||||
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
|
||||
* (lower or higher than the given value).
|
||||
* @param uiMode the required interface mode.
|
||||
* @param nightMode true, if the device should be in the night mode, false if should not.
|
||||
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
|
||||
* @param smallestWidth the required smallest width of the screen.
|
||||
*/
|
||||
inline fun <T : Any> Context.configuration(
|
||||
screenSize: ScreenSize? = null,
|
||||
density: ClosedRange<Int>? = null,
|
||||
language: String? = null,
|
||||
orientation: Orientation? = null,
|
||||
long: Boolean? = null,
|
||||
fromSdk: Int? = null,
|
||||
sdk: Int? = null,
|
||||
uiMode: UiMode? = null,
|
||||
nightMode: Boolean? = null,
|
||||
rightToLeft: Boolean? = null,
|
||||
smallestWidth: Int? = null,
|
||||
f: () -> T,
|
||||
): T? = if (AnkoInternals.testConfiguration(
|
||||
this, screenSize, density, language, orientation, long,
|
||||
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
|
||||
)
|
||||
) f() else null
|
||||
|
||||
/**
|
||||
* Execute [f] if the device configuration matches all given constraints.
|
||||
* You can use named arguments to provide only the relevant constraints.
|
||||
* All null constraints are ignored.
|
||||
*
|
||||
* @param screenSize the required screen size.
|
||||
* @param density the required screen density.
|
||||
* @param language the required system language.
|
||||
* @param orientation the current screen orientation.
|
||||
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
|
||||
* @param fromSdk the minimal SDK version for code to execute.
|
||||
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
|
||||
* (lower or higher than the given value).
|
||||
* @param uiMode the required interface mode.
|
||||
* @param nightMode true, if the device should be in the night mode, false if should not.
|
||||
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
|
||||
* @param smallestWidth the required smallest width of the screen.
|
||||
*/
|
||||
inline fun <T : Any> Activity.configuration(
|
||||
screenSize: ScreenSize? = null,
|
||||
density: ClosedRange<Int>? = null,
|
||||
language: String? = null,
|
||||
orientation: Orientation? = null,
|
||||
long: Boolean? = null,
|
||||
fromSdk: Int? = null,
|
||||
sdk: Int? = null,
|
||||
uiMode: UiMode? = null,
|
||||
nightMode: Boolean? = null,
|
||||
rightToLeft: Boolean? = null,
|
||||
smallestWidth: Int? = null,
|
||||
f: () -> T,
|
||||
): T? = if (AnkoInternals.testConfiguration(
|
||||
this, screenSize, density, language, orientation, long,
|
||||
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
|
||||
)
|
||||
) f() else null
|
||||
|
||||
/**
|
||||
* Execute [f] if the device configuration matches all given constraints.
|
||||
* You can use named arguments to provide only the relevant constraints.
|
||||
* All null constraints are ignored.
|
||||
*
|
||||
* @param screenSize the required screen size.
|
||||
* @param density the required screen density.
|
||||
* @param language the required system language.
|
||||
* @param orientation the current screen orientation.
|
||||
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
|
||||
* @param fromSdk the minimal SDK version for code to execute.
|
||||
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
|
||||
* (lower or higher than the given value).
|
||||
* @param uiMode the required interface mode.
|
||||
* @param nightMode true, if the device should be in the night mode, false if should not.
|
||||
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
|
||||
* @param smallestWidth the required smallest width of the screen.
|
||||
*/
|
||||
inline fun <T : Any> AnkoContext<*>.configuration(
|
||||
screenSize: ScreenSize? = null,
|
||||
density: ClosedRange<Int>? = null,
|
||||
language: String? = null,
|
||||
orientation: Orientation? = null,
|
||||
long: Boolean? = null,
|
||||
fromSdk: Int? = null,
|
||||
sdk: Int? = null,
|
||||
uiMode: UiMode? = null,
|
||||
nightMode: Boolean? = null,
|
||||
rightToLeft: Boolean? = null,
|
||||
smallestWidth: Int? = null,
|
||||
f: () -> T,
|
||||
): T? = if (AnkoInternals.testConfiguration(
|
||||
ctx, screenSize, density, language, orientation, long,
|
||||
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
|
||||
)
|
||||
) f() else null
|
||||
|
||||
/**
|
||||
* Execute [f] if the device configuration matches all given constraints.
|
||||
* You can use named arguments to provide only the relevant constraints.
|
||||
* All null constraints are ignored.
|
||||
*
|
||||
* @param screenSize the required screen size.
|
||||
* @param density the required screen density.
|
||||
* @param language the required system language.
|
||||
* @param orientation the current screen orientation.
|
||||
* @param long true, if the screen layout is long. See Configuration.SCREENLAYOUT_LONG_YES for more information.
|
||||
* @param fromSdk the minimal SDK version for code to execute.
|
||||
* @param sdk the target SDK version. Code will not be executed if the device Android SDK version is different
|
||||
* (lower or higher than the given value).
|
||||
* @param uiMode the required interface mode.
|
||||
* @param nightMode true, if the device should be in the night mode, false if should not.
|
||||
* @param rightToLeft true, if the device locale should be a right-to-left one, false if should not.
|
||||
* @param smallestWidth the required smallest width of the screen.
|
||||
*/
|
||||
@Deprecated(message = "Use support library fragments instead. Framework fragments were deprecated in API 28.")
|
||||
inline fun <T : Any> Fragment.configuration(
|
||||
screenSize: ScreenSize? = null,
|
||||
density: ClosedRange<Int>? = null,
|
||||
language: String? = null,
|
||||
orientation: Orientation? = null,
|
||||
long: Boolean? = null,
|
||||
fromSdk: Int? = null,
|
||||
sdk: Int? = null,
|
||||
uiMode: UiMode? = null,
|
||||
nightMode: Boolean? = null,
|
||||
rightToLeft: Boolean? = null,
|
||||
smallestWidth: Int? = null,
|
||||
f: () -> T,
|
||||
): T? {
|
||||
val act = activity
|
||||
return if (act != null) {
|
||||
if (AnkoInternals.testConfiguration(
|
||||
act, screenSize, density, language, orientation, long,
|
||||
fromSdk, sdk, uiMode, nightMode, rightToLeft, smallestWidth
|
||||
)
|
||||
) f() else null
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] only if the current Android SDK version is [version] or older.
|
||||
* Do nothing otherwise.
|
||||
*/
|
||||
inline fun doBeforeSdk(version: Int, f: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT <= version) f()
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] only if the current Android SDK version is [version] or newer.
|
||||
* Do nothing otherwise.
|
||||
*/
|
||||
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
|
||||
inline fun doFromSdk(version: Int, f: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT >= version) f()
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] only if the current Android SDK version is [version].
|
||||
* Do nothing otherwise.
|
||||
*/
|
||||
inline fun doIfSdk(version: Int, f: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT == version) f()
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of the [attempt] function.
|
||||
* Either [value] or [error] is not null.
|
||||
*
|
||||
* @property value the return value if code execution was finished without an exception, null otherwise.
|
||||
* @property error a caught [Throwable] or null if nothing was caught.
|
||||
*/
|
||||
data class AttemptResult<out T> @PublishedApi internal constructor(
|
||||
val value: T?,
|
||||
val error: Throwable?,
|
||||
) {
|
||||
inline fun <R> then(f: (T) -> R): AttemptResult<R> {
|
||||
if (isError) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this as AttemptResult<R>
|
||||
}
|
||||
|
||||
return attempt {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
f(value as T)
|
||||
}
|
||||
}
|
||||
|
||||
inline val isError: Boolean
|
||||
get() = error != null
|
||||
|
||||
inline val hasValue: Boolean
|
||||
get() = error == null
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [f] and return the result or an exception, if an exception was occurred.
|
||||
*/
|
||||
inline fun <T> attempt(f: () -> T): AttemptResult<T> {
|
||||
var value: T? = null
|
||||
var error: Throwable? = null
|
||||
try {
|
||||
value = f()
|
||||
} catch (t: Throwable) {
|
||||
error = t
|
||||
}
|
||||
return AttemptResult(value, error)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.text.InputType
|
||||
|
||||
enum class InputConstraints(val value: Int) {
|
||||
PASSWORD(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
|
||||
inline fun <reified T : Activity> Context.startActivity(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartActivity(this, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Activity> AnkoContext<*>.startActivity(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartActivity(ctx, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Activity> Fragment.startActivity(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartActivity(requireActivity(), T::class.java, params)
|
||||
|
||||
inline fun <reified T : Activity> Activity.startActivityForResult(
|
||||
requestCode: Int,
|
||||
vararg params: Pair<String, Any?>,
|
||||
) =
|
||||
AnkoInternals.internalStartActivityForResult(this, T::class.java, requestCode, params)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(
|
||||
message = "startActivityForResult is deprecated at Android 12",
|
||||
replaceWith = ReplaceWith("ActivityResult")
|
||||
)
|
||||
inline fun <reified T : Activity> Fragment.startActivityForResult(
|
||||
requestCode: Int,
|
||||
vararg params: Pair<String, Any?>,
|
||||
) =
|
||||
startActivityForResult(
|
||||
AnkoInternals.createIntent(requireActivity(), T::class.java, params),
|
||||
requestCode
|
||||
)
|
||||
|
||||
inline fun <reified T : Service> Context.startService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartService(this, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Service> AnkoContext<*>.startService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartService(ctx, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Service> Fragment.startService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStartService(requireActivity(), T::class.java, params)
|
||||
|
||||
inline fun <reified T : Service> Context.stopService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStopService(this, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Service> AnkoContext<*>.stopService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStopService(ctx, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Service> Fragment.stopService(vararg params: Pair<String, Any?>) =
|
||||
AnkoInternals.internalStopService(requireActivity(), T::class.java, params)
|
||||
|
||||
inline fun <reified T : Any> Context.intentFor(vararg params: Pair<String, Any?>): Intent =
|
||||
AnkoInternals.createIntent(this, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Any> AnkoContext<*>.intentFor(vararg params: Pair<String, Any?>): Intent =
|
||||
AnkoInternals.createIntent(ctx, T::class.java, params)
|
||||
|
||||
inline fun <reified T : Any> Fragment.intentFor(vararg params: Pair<String, Any?>): Intent =
|
||||
AnkoInternals.createIntent(requireActivity(), T::class.java, params)
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_CLEAR_TASK] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.clearTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_CLEAR_TOP] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.clearTop(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Deprecated in Android",
|
||||
replaceWith = ReplaceWith("org.jetbrains.anko.newDocument")
|
||||
)
|
||||
inline fun Intent.clearWhenTaskReset(): Intent = apply {
|
||||
@Suppress("DEPRECATION")
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_NEW_DOCUMENT] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.newDocument(): Intent = apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.excludeFromRecents(): Intent =
|
||||
apply { addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_MULTIPLE_TASK] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.multipleTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_NEW_TASK] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.newTask(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_NO_ANIMATION] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.noAnimation(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_NO_HISTORY] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.noHistory(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) }
|
||||
|
||||
/**
|
||||
* Add the [Intent.FLAG_ACTIVITY_SINGLE_TOP] flag to the [Intent].
|
||||
*
|
||||
* @return the same intent with the flag applied.
|
||||
*/
|
||||
inline fun Intent.singleTop(): Intent = apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) }
|
||||
|
||||
inline fun AnkoContext<*>.browse(url: String, newTask: Boolean = false) = ctx.browse(url, newTask)
|
||||
|
||||
inline fun Fragment.browse(url: String, newTask: Boolean = false) =
|
||||
requireActivity().browse(url, newTask)
|
||||
|
||||
fun Context.browse(url: String, newTask: Boolean = false) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(url)
|
||||
if (newTask) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
startActivity(intent)
|
||||
// ActivityNotFoundException
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.share(text: String, subject: String = "", title: String? = null) =
|
||||
ctx.share(text, subject, title)
|
||||
|
||||
inline fun Fragment.share(text: String, subject: String = "", title: String? = null) =
|
||||
requireActivity().share(text, subject, title)
|
||||
|
||||
fun Context.share(text: String, subject: String = "", title: String? = null) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
startActivity(Intent.createChooser(intent, title))
|
||||
// ActivityNotFoundException
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.email(email: String, subject: String = "", text: String = "") =
|
||||
ctx.email(email, subject, text)
|
||||
|
||||
inline fun Fragment.email(email: String, subject: String = "", text: String = "") =
|
||||
requireActivity().email(email, subject, text)
|
||||
|
||||
@SuppressLint("QueryPermissionsNeeded")
|
||||
fun Context.email(email: String, subject: String = "", text: String = ""): Boolean {
|
||||
val intent = Intent(Intent.ACTION_SENDTO)
|
||||
intent.data = Uri.parse("mailto:")
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
|
||||
if (subject.isNotEmpty())
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
if (text.isNotEmpty())
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.makeCall(number: String) = ctx.makeCall(number)
|
||||
|
||||
inline fun Fragment.makeCall(number: String) = requireActivity().makeCall(number)
|
||||
|
||||
fun Context.makeCall(number: String) {
|
||||
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$number"))
|
||||
startActivity(intent)
|
||||
// ActivityNotFoundException
|
||||
}
|
||||
|
||||
inline fun AnkoContext<*>.sendSMS(number: String, text: String = "") =
|
||||
ctx.sendSMS(number, text)
|
||||
|
||||
inline fun Fragment.sendSMS(number: String, text: String = "") =
|
||||
requireActivity().sendSMS(number, text)
|
||||
|
||||
fun Context.sendSMS(number: String, text: String = "") {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("sms:$number"))
|
||||
intent.putExtra("sms_body", text)
|
||||
startActivity(intent)
|
||||
// ActivityNotFoundException
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE", "MatchingDeclarationName", "NAME_SHADOWING")
|
||||
@file:JvmName("Logging")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Interface for the Anko logger.
|
||||
* Normally you should pass the logger tag to the [Log] methods, such as [Log.d] or [Log.e].
|
||||
* This can be inconvenient because you should store the tag somewhere or hardcode it,
|
||||
* which is considered to be a bad practice.
|
||||
*
|
||||
* Instead of hardcoding tags, Anko provides an [AnkoLogger] interface. You can just add the interface to
|
||||
* any of your classes, and use any of the provided extension functions, such as
|
||||
* [AnkoLogger.debug] or [AnkoLogger.error].
|
||||
*
|
||||
* The tag is the simple class name by default, but you can change it to anything you want just
|
||||
* by overriding the [loggerTag] property.
|
||||
*/
|
||||
interface AnkoLogger {
|
||||
/**
|
||||
* The logger tag used in extension functions for the [AnkoLogger].
|
||||
* Note that the tag length should not be more than 23 symbols.
|
||||
*/
|
||||
val loggerTag: String
|
||||
get() = getTag(javaClass)
|
||||
}
|
||||
|
||||
fun AnkoLogger(clazz: Class<*>): AnkoLogger = object : AnkoLogger {
|
||||
override val loggerTag = getTag(clazz)
|
||||
}
|
||||
|
||||
fun AnkoLogger(tag: String): AnkoLogger = object : AnkoLogger {
|
||||
init {
|
||||
assert(tag.length <= 23) { "The maximum tag length is 23, got $tag" }
|
||||
}
|
||||
|
||||
override val loggerTag = tag
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> AnkoLogger(): AnkoLogger = AnkoLogger(T::class.java)
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.VERBOSE] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.VERBOSE].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.v].
|
||||
*/
|
||||
fun AnkoLogger.verbose(message: Any?, thr: Throwable? = null) {
|
||||
log(this, message, thr, Log.VERBOSE,
|
||||
{ tag, msg -> Log.v(tag, msg) },
|
||||
{ tag, msg, thr -> Log.v(tag, msg, thr) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.DEBUG] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.DEBUG].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.d].
|
||||
*/
|
||||
fun AnkoLogger.debug(message: Any?, thr: Throwable? = null) {
|
||||
log(this, message, thr, Log.DEBUG,
|
||||
{ tag, msg -> Log.d(tag, msg) },
|
||||
{ tag, msg, thr -> Log.d(tag, msg, thr) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.INFO] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.INFO]
|
||||
* (it is the default level).
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.i].
|
||||
*/
|
||||
fun AnkoLogger.info(message: Any?, thr: Throwable? = null) {
|
||||
log(this, message, thr, Log.INFO,
|
||||
{ tag, msg -> Log.i(tag, msg) },
|
||||
{ tag, msg, thr -> Log.i(tag, msg, thr) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.WARN] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.WARN].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.w].
|
||||
*/
|
||||
fun AnkoLogger.warn(message: Any?, thr: Throwable? = null) {
|
||||
log(this, message, thr, Log.WARN,
|
||||
{ tag, msg -> Log.w(tag, msg) },
|
||||
{ tag, msg, thr -> Log.w(tag, msg, thr) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.ERROR] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.ERROR].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.e].
|
||||
*/
|
||||
fun AnkoLogger.error(message: Any?, thr: Throwable? = null) {
|
||||
log(this, message, thr, Log.ERROR,
|
||||
{ tag, msg -> Log.e(tag, msg) },
|
||||
{ tag, msg, thr -> Log.e(tag, msg, thr) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the "What a Terrible Failure" severity.
|
||||
* Report an exception that should never happen.
|
||||
*
|
||||
* @param message the message text to log. `null` value will be represent as "null", for any other value
|
||||
* the [Any.toString] will be invoked.
|
||||
* @param thr an exception to log (optional).
|
||||
*
|
||||
* @see [Log.wtf].
|
||||
*/
|
||||
fun AnkoLogger.wtf(message: Any?, thr: Throwable? = null) {
|
||||
if (thr != null) {
|
||||
Log.wtf(loggerTag, message?.toString() ?: "null", thr)
|
||||
} else {
|
||||
Log.wtf(loggerTag, message?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.VERBOSE] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.VERBOSE].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the function that returns message text to log.
|
||||
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
|
||||
*
|
||||
* @see [Log.v].
|
||||
*/
|
||||
inline fun AnkoLogger.verbose(message: () -> Any?) {
|
||||
val tag = loggerTag
|
||||
if (Log.isLoggable(tag, Log.VERBOSE)) {
|
||||
Log.v(tag, message()?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.DEBUG] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.DEBUG].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the function that returns message text to log.
|
||||
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
|
||||
*
|
||||
* @see [Log.d].
|
||||
*/
|
||||
inline fun AnkoLogger.debug(message: () -> Any?) {
|
||||
val tag = loggerTag
|
||||
if (Log.isLoggable(tag, Log.DEBUG)) {
|
||||
Log.d(tag, message()?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.INFO] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.INFO].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the function that returns message text to log.
|
||||
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
|
||||
*
|
||||
* @see [Log.i].
|
||||
*/
|
||||
inline fun AnkoLogger.info(message: () -> Any?) {
|
||||
val tag = loggerTag
|
||||
if (Log.isLoggable(tag, Log.INFO)) {
|
||||
Log.i(tag, message()?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.WARN] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.WARN].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the function that returns message text to log.
|
||||
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
|
||||
*
|
||||
* @see [Log.w].
|
||||
*/
|
||||
inline fun AnkoLogger.warn(message: () -> Any?) {
|
||||
val tag = loggerTag
|
||||
if (Log.isLoggable(tag, Log.WARN)) {
|
||||
Log.w(tag, message()?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log message with the [Log.ERROR] severity.
|
||||
* Note that the log message will not be written if the current log level is above [Log.ERROR].
|
||||
* The default log level is [Log.INFO].
|
||||
*
|
||||
* @param message the function that returns message text to log.
|
||||
* `null` value will be represent as "null", for any other value the [Any.toString] will be invoked.
|
||||
*
|
||||
* @see [Log.e].
|
||||
*/
|
||||
inline fun AnkoLogger.error(message: () -> Any?) {
|
||||
val tag = loggerTag
|
||||
if (Log.isLoggable(tag, Log.ERROR)) {
|
||||
Log.e(tag, message()?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stack trace [String] of a throwable.
|
||||
*/
|
||||
inline fun Throwable.getStackTraceString(): String = Log.getStackTraceString(this)
|
||||
|
||||
private inline fun log(
|
||||
logger: AnkoLogger,
|
||||
message: Any?,
|
||||
thr: Throwable?,
|
||||
level: Int,
|
||||
f: (String, String) -> Unit,
|
||||
fThrowable: (String, String, Throwable) -> Unit,
|
||||
) {
|
||||
val tag = logger.loggerTag
|
||||
if (Log.isLoggable(tag, level)) {
|
||||
if (thr != null) {
|
||||
fThrowable(tag, message?.toString() ?: "null", thr)
|
||||
} else {
|
||||
f(tag, message?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTag(clazz: Class<*>): String {
|
||||
val tag = clazz.simpleName
|
||||
return if (tag.length <= 23) {
|
||||
tag
|
||||
} else {
|
||||
tag.substring(0, 23)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package org.jetbrains.anko
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.SubMenu
|
||||
|
||||
/**
|
||||
* Create a plain menu item
|
||||
*/
|
||||
fun Menu.item(
|
||||
title: CharSequence, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
checkable: Boolean = false,
|
||||
): MenuItem =
|
||||
add(title).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = checkable
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a menu item and configure it
|
||||
*/
|
||||
inline fun Menu.item(
|
||||
title: CharSequence, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
checkable: Boolean = false,
|
||||
configure: MenuItem.() -> Unit,
|
||||
): MenuItem =
|
||||
add(title).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = checkable
|
||||
configure()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a menu item with title from resources
|
||||
*/
|
||||
fun Menu.item(
|
||||
/*@StringRes*/
|
||||
titleRes: Int, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
checkable: Boolean = false,
|
||||
): MenuItem =
|
||||
add(titleRes).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = checkable
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a menu item with title from resources and configure it
|
||||
*/
|
||||
inline fun Menu.item(
|
||||
/*@StringRes*/
|
||||
titleRes: Int, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
checkable: Boolean = false,
|
||||
configure: MenuItem.() -> Unit,
|
||||
): MenuItem =
|
||||
add(titleRes).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = checkable
|
||||
configure()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a submenu
|
||||
*/
|
||||
fun Menu.subMenu(title: CharSequence): SubMenu =
|
||||
addSubMenu(title)
|
||||
|
||||
/**
|
||||
* Create a submenu and configure it
|
||||
*/
|
||||
inline fun Menu.subMenu(title: CharSequence, configure: SubMenu.() -> Unit): SubMenu =
|
||||
addSubMenu(title).apply { configure() }
|
||||
|
||||
/**
|
||||
* Create a submenu with title from resources
|
||||
*/
|
||||
fun Menu.subMenu(/*@StringRes*/ titleRes: Int): SubMenu =
|
||||
addSubMenu(titleRes)
|
||||
|
||||
/**
|
||||
* Create a submenu with title from resources and configure it
|
||||
*/
|
||||
inline fun Menu.subMenu(/*@StringRes*/ titleRes: Int, configure: SubMenu.() -> Unit): SubMenu =
|
||||
addSubMenu(titleRes).apply { configure() }
|
||||
|
||||
/**
|
||||
* Create a checkable menu item for use in NavigationView
|
||||
*/
|
||||
fun Menu.navigationItem(title: CharSequence, /*@DrawableRes*/ icon: Int = 0) {
|
||||
add(title).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a navigation item with OnClickListener
|
||||
*/
|
||||
inline fun Menu.navigationItem(
|
||||
title: CharSequence, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
crossinline onClick: () -> Unit,
|
||||
) {
|
||||
add(title).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = true
|
||||
setOnMenuItemClickListener {
|
||||
onClick()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a navigation item with title from resources
|
||||
*/
|
||||
fun Menu.navigationItem(/*@StringRes*/ titleRes: Int, /*@DrawableRes*/ icon: Int = 0) {
|
||||
add(titleRes).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a navigation item with title from resources and onClick listener
|
||||
*/
|
||||
inline fun Menu.navigationItem(
|
||||
/*@StringRes*/
|
||||
titleRes: Int, /*@DrawableRes*/
|
||||
icon: Int = 0,
|
||||
crossinline onClick: () -> Unit,
|
||||
) {
|
||||
add(titleRes).apply {
|
||||
setIcon(icon)
|
||||
isCheckable = true
|
||||
setOnMenuItemClickListener {
|
||||
onClick()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OnClickListener on a menu item
|
||||
*/
|
||||
inline fun MenuItem.onClick(consume: Boolean = true, crossinline action: () -> Unit) {
|
||||
setOnMenuItemClickListener { action(); consume }
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
@file:JvmName("Sdk28PropertiesKt")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
|
||||
var android.view.View.backgroundColor: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setBackgroundColor(v)
|
||||
|
||||
var android.view.View.backgroundResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setBackgroundResource(v)
|
||||
|
||||
var android.widget.ImageView.imageResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setImageResource(v)
|
||||
|
||||
var android.widget.ImageView.imageURI: android.net.Uri?
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setImageURI(v)
|
||||
|
||||
var android.widget.ImageView.imageBitmap: android.graphics.Bitmap?
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setImageBitmap(v)
|
||||
|
||||
var android.widget.TextView.textColor: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setTextColor(v)
|
||||
|
||||
var android.widget.TextView.hintTextColor: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setHintTextColor(v)
|
||||
|
||||
var android.widget.TextView.linkTextColor: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setLinkTextColor(v)
|
||||
|
||||
var android.widget.TextView.lines: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setLines(v)
|
||||
|
||||
var android.widget.TextView.singleLine: Boolean
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) {
|
||||
isSingleLine = v
|
||||
}
|
||||
|
||||
var android.widget.RelativeLayout.horizontalGravity: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setHorizontalGravity(v)
|
||||
|
||||
var android.widget.RelativeLayout.verticalGravity: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setVerticalGravity(v)
|
||||
|
||||
var android.widget.LinearLayout.horizontalGravity: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setHorizontalGravity(v)
|
||||
|
||||
var android.widget.LinearLayout.verticalGravity: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setVerticalGravity(v)
|
||||
|
||||
//var android.widget.Gallery.gravity: Int
|
||||
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
|
||||
// set(v) = setGravity(v)
|
||||
|
||||
var android.widget.AbsListView.selectorResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setSelector(v)
|
||||
|
||||
//var android.widget.CalendarView.selectedDateVerticalBarResource: Int
|
||||
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
|
||||
// set(v) = setSelectedDateVerticalBar(v)
|
||||
|
||||
var android.widget.CheckedTextView.checkMarkDrawableResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setCheckMarkDrawable(v)
|
||||
|
||||
var android.widget.CompoundButton.buttonDrawableResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setButtonDrawable(v)
|
||||
|
||||
//var android.widget.TabWidget.leftStripDrawableResource: Int
|
||||
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
|
||||
// set(v) = setLeftStripDrawable(v)
|
||||
//
|
||||
//var android.widget.TabWidget.rightStripDrawableResource: Int
|
||||
// @Deprecated(AnkoInternals.NO_GETTER, level = DeprecationLevel.ERROR) get() = AnkoInternals.noGetter()
|
||||
// set(v) = setRightStripDrawable(v)
|
||||
|
||||
var android.widget.TextView.hintResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setHint(v)
|
||||
|
||||
var android.widget.TextView.textResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setText(v)
|
||||
|
||||
var android.widget.Toolbar.logoResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setLogo(v)
|
||||
|
||||
var android.widget.Toolbar.logoDescriptionResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setLogoDescription(v)
|
||||
|
||||
var android.widget.Toolbar.navigationContentDescriptionResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setNavigationContentDescription(v)
|
||||
|
||||
var android.widget.Toolbar.navigationIconResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setNavigationIcon(v)
|
||||
|
||||
var android.widget.Toolbar.subtitleResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setSubtitle(v)
|
||||
|
||||
var android.widget.Toolbar.titleResource: Int
|
||||
@Deprecated(
|
||||
AnkoInternals.NO_GETTER,
|
||||
level = DeprecationLevel.ERROR
|
||||
) get() = AnkoInternals.noGetter()
|
||||
set(v) = setTitle(v)
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout.*
|
||||
import androidx.annotation.IdRes
|
||||
|
||||
/**
|
||||
* Place the current View above [view].
|
||||
* It is an alias for [above].
|
||||
*/
|
||||
fun LayoutParams.topOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ABOVE, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View above [view].
|
||||
*/
|
||||
fun LayoutParams.above(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ABOVE, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View below [view].
|
||||
* It is an alias for [below].
|
||||
*/
|
||||
fun LayoutParams.bottomOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(BELOW, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View below [view].
|
||||
*/
|
||||
fun LayoutParams.below(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(BELOW, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View to the left of [view].
|
||||
*/
|
||||
inline fun LayoutParams.leftOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(LEFT_OF, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View to the start of [view].
|
||||
*/
|
||||
inline fun LayoutParams.startOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(START_OF, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View to the right of [view].
|
||||
*/
|
||||
inline fun LayoutParams.rightOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(RIGHT_OF, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View to the end of [view].
|
||||
*/
|
||||
inline fun LayoutParams.endOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(END_OF, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View left attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameLeft(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_LEFT, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View start attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameStart(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_START, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View top attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameTop(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_TOP, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View right attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameRight(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_RIGHT, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View end attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameEnd(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_END, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current View bottom attribute the same as for [view].
|
||||
*/
|
||||
inline fun LayoutParams.sameBottom(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_BOTTOM, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current View above the View with a given [id].
|
||||
* It is an alias for [above].
|
||||
*/
|
||||
inline fun LayoutParams.topOf(@IdRes id: Int) = addRule(ABOVE, id)
|
||||
|
||||
/**
|
||||
* Place the current View above the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.above(@IdRes id: Int) = addRule(ABOVE, id)
|
||||
|
||||
/**
|
||||
* Place the current View below the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.below(@IdRes id: Int) = addRule(BELOW, id)
|
||||
|
||||
/**
|
||||
* Place the current View below the View with a given [id].
|
||||
* It is an alias for [below].
|
||||
*/
|
||||
inline fun LayoutParams.bottomOf(@IdRes id: Int) = addRule(BELOW, id)
|
||||
|
||||
/**
|
||||
* Place the current View to the left of the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.leftOf(@IdRes id: Int) = addRule(LEFT_OF, id)
|
||||
|
||||
/**
|
||||
* Place the current View to the start of the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.startOf(@IdRes id: Int): Unit = addRule(START_OF, id)
|
||||
|
||||
/**
|
||||
* Place the current View to the left of the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.rightOf(@IdRes id: Int) = addRule(RIGHT_OF, id)
|
||||
|
||||
/**
|
||||
* Place the current View to the end of the View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.endOf(@IdRes id: Int): Unit = addRule(END_OF, id)
|
||||
|
||||
/**
|
||||
* Set the current View left attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameLeft(@IdRes id: Int) = addRule(ALIGN_LEFT, id)
|
||||
|
||||
/**
|
||||
* Set the current View start attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameStart(@IdRes id: Int): Unit = addRule(ALIGN_START, id)
|
||||
|
||||
/**
|
||||
* Set the current View top attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameTop(@IdRes id: Int) = addRule(ALIGN_TOP, id)
|
||||
|
||||
/**
|
||||
* Set the current View right attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameRight(@IdRes id: Int) = addRule(ALIGN_RIGHT, id)
|
||||
|
||||
/**
|
||||
* Set the current View end attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameEnd(@IdRes id: Int): Unit = addRule(ALIGN_END, id)
|
||||
|
||||
/**
|
||||
* Set the current View bottom attribute the same as for View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.sameBottom(@IdRes id: Int) = addRule(ALIGN_BOTTOM, id)
|
||||
|
||||
/**
|
||||
* Align the current View's start edge with another child's start edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignStart(@IdRes id: Int): Unit = addRule(ALIGN_START, id)
|
||||
|
||||
/**
|
||||
* Align the current View's end edge with another child's end edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignEnd(@IdRes id: Int): Unit = addRule(ALIGN_END, id)
|
||||
|
||||
/**
|
||||
* Align the current View's top edge with its parent's top edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentTop() = addRule(ALIGN_PARENT_TOP)
|
||||
|
||||
/**
|
||||
* Align the current View's right edge with its parent's right edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentRight() = addRule(ALIGN_PARENT_RIGHT)
|
||||
|
||||
/**
|
||||
* Align the current View's bottom edge with its parent's bottom edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentBottom() = addRule(ALIGN_PARENT_BOTTOM)
|
||||
|
||||
/**
|
||||
* Align the current View's left edge with its parent's left edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentLeft() = addRule(ALIGN_PARENT_LEFT)
|
||||
|
||||
/**
|
||||
* Center the child horizontally in its parent.
|
||||
*/
|
||||
inline fun LayoutParams.centerHorizontally() = addRule(CENTER_HORIZONTAL)
|
||||
|
||||
/**
|
||||
* Center the child vertically in its parent.
|
||||
*/
|
||||
inline fun LayoutParams.centerVertically() = addRule(CENTER_VERTICAL)
|
||||
|
||||
/**
|
||||
* Center the child horizontally and vertically in its parent.
|
||||
*/
|
||||
inline fun LayoutParams.centerInParent() = addRule(CENTER_IN_PARENT)
|
||||
|
||||
/**
|
||||
* Align the current View's start edge with its parent's start edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentStart(): Unit = addRule(ALIGN_PARENT_START)
|
||||
|
||||
/**
|
||||
* Align the current View's end edge with its parent's end edge.
|
||||
*/
|
||||
inline fun LayoutParams.alignParentEnd(): Unit = addRule(ALIGN_PARENT_END)
|
||||
|
||||
/**
|
||||
* Positions the baseline of this view on the baseline of the given anchor [view].
|
||||
*/
|
||||
inline fun LayoutParams.baselineOf(view: View) {
|
||||
val id = view.id
|
||||
if (id == View.NO_ID) throw AnkoException("Id is not set for $view")
|
||||
addRule(ALIGN_BASELINE, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the baseline of this view on the baseline of the anchor View with a given [id].
|
||||
*/
|
||||
inline fun LayoutParams.baselineOf(@IdRes id: Int) = addRule(ALIGN_BASELINE, id)
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
inline fun <D : DialogInterface> AnkoContext<*>.selector(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
noinline onClick: (DialogInterface, CharSequence, Int) -> Unit,
|
||||
) = ctx.selector(factory, title, items, onClick)
|
||||
|
||||
inline fun <D : DialogInterface> Fragment.selector(
|
||||
noinline factory: AlertBuilderFactory<D>,
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
noinline onClick: (DialogInterface, CharSequence, Int) -> Unit,
|
||||
) = requireActivity().selector(factory, title, items, onClick)
|
||||
|
||||
fun <D : DialogInterface> Context.selector(
|
||||
factory: AlertBuilderFactory<D>,
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
onClick: (DialogInterface, CharSequence, Int) -> Unit,
|
||||
) {
|
||||
with(factory(this)) {
|
||||
if (title != null) {
|
||||
this.title = title
|
||||
}
|
||||
items(items, onClick)
|
||||
show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
@file:JvmName("Sdk28ServicesKt")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.app.*
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.app.usage.NetworkStatsManager
|
||||
import android.app.usage.StorageStatsManager
|
||||
import android.app.usage.UsageStatsManager
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.RestrictionsManager
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.hardware.ConsumerIrManager
|
||||
import android.hardware.SensorManager
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.location.LocationManager
|
||||
import android.media.AudioManager
|
||||
import android.media.midi.MidiManager
|
||||
import android.media.projection.MediaProjectionManager
|
||||
import android.media.session.MediaSessionManager
|
||||
import android.media.tv.TvInputManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.nsd.NsdManager
|
||||
import android.net.wifi.WifiManager
|
||||
import android.net.wifi.aware.WifiAwareManager
|
||||
import android.net.wifi.p2p.WifiP2pManager
|
||||
import android.nfc.NfcManager
|
||||
import android.os.BatteryManager
|
||||
import android.os.HardwarePropertiesManager
|
||||
import android.os.PowerManager
|
||||
import android.os.UserManager
|
||||
import android.os.health.SystemHealthManager
|
||||
import android.os.storage.StorageManager
|
||||
import android.print.PrintManager
|
||||
import android.telecom.TelecomManager
|
||||
import android.telephony.CarrierConfigManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.view.WindowManager
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import android.view.accessibility.CaptioningManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.view.textclassifier.TextClassificationManager
|
||||
|
||||
/** Returns the AccessibilityManager instance. **/
|
||||
val Context.accessibilityManager: AccessibilityManager
|
||||
get() = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
||||
|
||||
/** Returns the AccountManager instance. **/
|
||||
val Context.accountManager: AccountManager
|
||||
get() = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
|
||||
|
||||
/** Returns the ActivityManager instance. **/
|
||||
val Context.activityManager: ActivityManager
|
||||
get() = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
|
||||
/** Returns the AlarmManager instance. **/
|
||||
val Context.alarmManager: AlarmManager
|
||||
get() = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
|
||||
/** Returns the AppOpsManager instance. **/
|
||||
val Context.appOpsManager: AppOpsManager
|
||||
get() = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
||||
|
||||
/** Returns the AudioManager instance. **/
|
||||
val Context.audioManager: AudioManager
|
||||
get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
|
||||
/** Returns the BatteryManager instance. **/
|
||||
val Context.batteryManager: BatteryManager
|
||||
get() = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
|
||||
|
||||
/** Returns the BluetoothManager instance. **/
|
||||
val Context.bluetoothManager: BluetoothManager
|
||||
get() = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
|
||||
/** Returns the CameraManager instance. **/
|
||||
val Context.cameraManager: CameraManager
|
||||
get() = getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
|
||||
/** Returns the CaptioningManager instance. **/
|
||||
val Context.captioningManager: CaptioningManager
|
||||
get() = getSystemService(Context.CAPTIONING_SERVICE) as CaptioningManager
|
||||
|
||||
/** Returns the CarrierConfigManager instance. **/
|
||||
val Context.carrierConfigManager: CarrierConfigManager
|
||||
get() = getSystemService(Context.CARRIER_CONFIG_SERVICE) as CarrierConfigManager
|
||||
|
||||
/** Returns the ClipboardManager instance. **/
|
||||
val Context.clipboardManager: ClipboardManager
|
||||
get() = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
/** Returns the CompanionDeviceManager instance. **/
|
||||
val Context.companionDeviceManager: CompanionDeviceManager
|
||||
get() = getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
|
||||
|
||||
/** Returns the ConnectivityManager instance. **/
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
/** Returns the ConsumerIrManager instance. **/
|
||||
val Context.consumerIrManager: ConsumerIrManager
|
||||
get() = getSystemService(Context.CONSUMER_IR_SERVICE) as ConsumerIrManager
|
||||
|
||||
/** Returns the DevicePolicyManager instance. **/
|
||||
val Context.devicePolicyManager: DevicePolicyManager
|
||||
get() = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
||||
|
||||
/** Returns the DisplayManager instance. **/
|
||||
val Context.displayManager: DisplayManager
|
||||
get() = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
|
||||
/** Returns the DownloadManager instance. **/
|
||||
val Context.downloadManager: DownloadManager
|
||||
get() = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
///** Returns the EuiccManager instance. **/
|
||||
//val Context.euiccManager: EuiccManager
|
||||
// get() = getSystemService(Context.EUICC_SERVICE) as EuiccManager
|
||||
|
||||
///** Returns the FingerprintManager instance. **/
|
||||
//val Context.fingerprintManager: FingerprintManager
|
||||
// get() = getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
|
||||
|
||||
/** Returns the HardwarePropertiesManager instance. **/
|
||||
val Context.hardwarePropertiesManager: HardwarePropertiesManager
|
||||
get() = getSystemService(Context.HARDWARE_PROPERTIES_SERVICE) as HardwarePropertiesManager
|
||||
|
||||
/** Returns the InputManager instance. **/
|
||||
val Context.inputManager: InputManager
|
||||
get() = getSystemService(Context.INPUT_SERVICE) as InputManager
|
||||
|
||||
/** Returns the InputMethodManager instance. **/
|
||||
val Context.inputMethodManager: InputMethodManager
|
||||
get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
||||
/** Returns the KeyguardManager instance. **/
|
||||
val Context.keyguardManager: KeyguardManager
|
||||
get() = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
|
||||
/** Returns the LocationManager instance. **/
|
||||
val Context.locationManager: LocationManager
|
||||
get() = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
/** Returns the MediaProjectionManager instance. **/
|
||||
val Context.mediaProjectionManager: MediaProjectionManager
|
||||
get() = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
|
||||
|
||||
/** Returns the MediaSessionManager instance. **/
|
||||
val Context.mediaSessionManager: MediaSessionManager
|
||||
get() = getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager
|
||||
|
||||
/** Returns the MidiManager instance. **/
|
||||
val Context.midiManager: MidiManager
|
||||
get() = getSystemService(Context.MIDI_SERVICE) as MidiManager
|
||||
|
||||
/** Returns the NetworkStatsManager instance. **/
|
||||
val Context.networkStatsManager: NetworkStatsManager
|
||||
get() = getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager
|
||||
|
||||
/** Returns the NfcManager instance. **/
|
||||
val Context.nfcManager: NfcManager
|
||||
get() = getSystemService(Context.NFC_SERVICE) as NfcManager
|
||||
|
||||
/** Returns the NotificationManager instance. **/
|
||||
val Context.notificationManager: NotificationManager
|
||||
get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
/** Returns the NsdManager instance. **/
|
||||
val Context.nsdManager: NsdManager
|
||||
get() = getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||
|
||||
/** Returns the PowerManager instance. **/
|
||||
val Context.powerManager: PowerManager
|
||||
get() = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
|
||||
/** Returns the PrintManager instance. **/
|
||||
val Context.printManager: PrintManager
|
||||
get() = getSystemService(Context.PRINT_SERVICE) as PrintManager
|
||||
|
||||
/** Returns the RestrictionsManager instance. **/
|
||||
val Context.restrictionsManager: RestrictionsManager
|
||||
get() = getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
|
||||
|
||||
/** Returns the SearchManager instance. **/
|
||||
val Context.searchManager: SearchManager
|
||||
get() = getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
|
||||
/** Returns the SensorManager instance. **/
|
||||
val Context.sensorManager: SensorManager
|
||||
get() = getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
|
||||
/** Returns the ShortcutManager instance. **/
|
||||
val Context.shortcutManager: ShortcutManager
|
||||
get() = getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
|
||||
|
||||
/** Returns the StorageManager instance. **/
|
||||
val Context.storageManager: StorageManager
|
||||
get() = getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||
|
||||
/** Returns the StorageStatsManager instance. **/
|
||||
val Context.storageStatsManager: StorageStatsManager
|
||||
get() = getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
|
||||
|
||||
/** Returns the SystemHealthManager instance. **/
|
||||
val Context.systemHealthManager: SystemHealthManager
|
||||
get() = getSystemService(Context.SYSTEM_HEALTH_SERVICE) as SystemHealthManager
|
||||
|
||||
/** Returns the TelecomManager instance. **/
|
||||
val Context.telecomManager: TelecomManager
|
||||
get() = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||
|
||||
/** Returns the TelephonyManager instance. **/
|
||||
val Context.telephonyManager: TelephonyManager
|
||||
get() = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
|
||||
/** Returns the TextClassificationManager instance. **/
|
||||
val Context.textClassificationManager: TextClassificationManager
|
||||
get() = getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager
|
||||
|
||||
/** Returns the TvInputManager instance. **/
|
||||
val Context.tvInputManager: TvInputManager
|
||||
get() = getSystemService(Context.TV_INPUT_SERVICE) as TvInputManager
|
||||
|
||||
/** Returns the UiModeManager instance. **/
|
||||
val Context.uiModeManager: UiModeManager
|
||||
get() = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
||||
|
||||
/** Returns the UsageStatsManager instance. **/
|
||||
val Context.usageStatsManager: UsageStatsManager
|
||||
get() = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
|
||||
|
||||
/** Returns the UsbManager instance. **/
|
||||
val Context.usbManager: UsbManager
|
||||
get() = getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
|
||||
/** Returns the UserManager instance. **/
|
||||
val Context.userManager: UserManager
|
||||
get() = getSystemService(Context.USER_SERVICE) as UserManager
|
||||
|
||||
/** Returns the WallpaperManager instance. **/
|
||||
val Context.wallpaperManager: WallpaperManager
|
||||
get() = getSystemService(Context.WALLPAPER_SERVICE) as WallpaperManager
|
||||
|
||||
/** Returns the WifiAwareManager instance. **/
|
||||
val Context.wifiAwareManager: WifiAwareManager
|
||||
get() = getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager
|
||||
|
||||
/** Returns the WifiManager instance. **/
|
||||
val Context.wifiManager: WifiManager
|
||||
get() = getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
/** Returns the WifiP2pManager instance. **/
|
||||
val Context.wifiP2pManager: WifiP2pManager
|
||||
get() = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
|
||||
|
||||
/** Returns the WindowManager instance. **/
|
||||
val Context.windowManager: WindowManager
|
||||
get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.anko
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
/**
|
||||
* Opens the [SharedPreferences.Editor], applies the [modifer] to it and then applies the changes asynchronously
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("edit(modifier)", "androidx.core.content.edit"))
|
||||
inline fun SharedPreferences.apply(modifier: SharedPreferences.Editor.() -> Unit) {
|
||||
val editor = this.edit()
|
||||
editor.modifier()
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the [SharedPreferences.Editor], applies the [modifer] to it and then applies the changes synchronously
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("edit(true, modifier)", "androidx.core.content.edit"))
|
||||
inline fun SharedPreferences.commit(modifier: SharedPreferences.Editor.() -> Unit) {
|
||||
val editor = this.edit()
|
||||
editor.modifier()
|
||||
editor.commit()
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
fun Resources.Theme.attr(@AttrRes attribute: Int): TypedValue {
|
||||
val typedValue = TypedValue()
|
||||
if (!resolveAttribute(attribute, typedValue, true)) {
|
||||
throw IllegalArgumentException("Failed to resolve attribute: $attribute")
|
||||
}
|
||||
|
||||
return typedValue
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun Resources.Theme.color(@AttrRes attribute: Int): Int {
|
||||
val attr = attr(attribute)
|
||||
if (attr.type < TypedValue.TYPE_FIRST_COLOR_INT || attr.type > TypedValue.TYPE_LAST_COLOR_INT) {
|
||||
throw IllegalArgumentException("Attribute value type is not color: $attribute")
|
||||
}
|
||||
|
||||
return attr.data
|
||||
}
|
||||
|
||||
fun Context.attr(@AttrRes attribute: Int): TypedValue = theme.attr(attribute)
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
fun Context.dimenAttr(@AttrRes attribute: Int): Int =
|
||||
TypedValue.complexToDimensionPixelSize(attr(attribute).data, resources.displayMetrics)
|
||||
|
||||
@ColorInt
|
||||
fun Context.colorAttr(@AttrRes attribute: Int): Int = theme.color(attribute)
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
inline fun AnkoContext<*>.dimenAttr(@AttrRes attribute: Int): Int = ctx.dimenAttr(attribute)
|
||||
|
||||
@ColorInt
|
||||
inline fun AnkoContext<*>.colorAttr(@AttrRes attribute: Int): Int = ctx.colorAttr(attribute)
|
||||
|
||||
inline fun AnkoContext<*>.attr(@AttrRes attribute: Int): TypedValue = ctx.attr(attribute)
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
inline fun View.dimenAttr(@AttrRes attribute: Int): Int = context.dimenAttr(attribute)
|
||||
|
||||
@ColorInt
|
||||
inline fun View.colorAttr(@AttrRes attribute: Int): Int = context.colorAttr(attribute)
|
||||
|
||||
inline fun View.attr(@AttrRes attribute: Int): TypedValue = context.attr(attribute)
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
inline fun Fragment.dimenAttr(@AttrRes attribute: Int): Int = requireContext().dimenAttr(attribute)
|
||||
|
||||
@ColorInt
|
||||
inline fun Fragment.colorAttr(@AttrRes attribute: Int): Int = requireContext().colorAttr(attribute)
|
||||
inline fun Fragment.attr(@AttrRes attribute: Int): TypedValue = requireContext().attr(attribute)
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun AnkoContext<*>.toast(message: Int) = ctx.toast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun Fragment.toast(message: Int) = requireContext().toast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun Context.toast(message: Int): Toast = Toast
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun AnkoContext<*>.toast(message: CharSequence) = ctx.toast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun Fragment.toast(message: CharSequence) = requireContext().toast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_SHORT] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun Context.toast(message: CharSequence): Toast = Toast
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun AnkoContext<*>.longToast(message: Int) = ctx.longToast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun Fragment.longToast(message: Int) = requireContext().longToast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text resource.
|
||||
*/
|
||||
inline fun Context.longToast(message: Int): Toast = Toast
|
||||
.makeText(this, message, Toast.LENGTH_LONG)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun AnkoContext<*>.longToast(message: CharSequence) = ctx.longToast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun Fragment.longToast(message: CharSequence) = requireContext().longToast(message)
|
||||
|
||||
/**
|
||||
* Display the simple Toast message with the [Toast.LENGTH_LONG] duration.
|
||||
*
|
||||
* @param message the message text.
|
||||
*/
|
||||
inline fun Context.longToast(message: CharSequence): Toast = Toast
|
||||
.makeText(this, message, Toast.LENGTH_LONG)
|
||||
.apply {
|
||||
show()
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE", "MatchingDeclarationName")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.view.View
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
|
||||
@DslMarker
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
annotation class AnkoViewDslMarker
|
||||
|
||||
/**
|
||||
* Apply [f] to this [View] and to all of its children recursively.
|
||||
*
|
||||
* @return the receiver.
|
||||
*/
|
||||
inline fun <T : View> T.applyRecursively(noinline f: (View) -> Unit): T {
|
||||
AnkoInternals.applyRecursively(this, f)
|
||||
return this
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.anko.appcompat.v7
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.jetbrains.anko.AlertBuilder
|
||||
import org.jetbrains.anko.AlertBuilderFactory
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
import org.jetbrains.anko.internals.AnkoInternals.NO_GETTER
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
val Appcompat: AlertBuilderFactory<AlertDialog> = ::AppcompatAlertBuilder
|
||||
|
||||
internal class AppcompatAlertBuilder(override val ctx: Context) : AlertBuilder<AlertDialog> {
|
||||
private val builder = AlertDialog.Builder(ctx)
|
||||
|
||||
override var title: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setTitle(value)
|
||||
}
|
||||
|
||||
override var titleResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setTitle(value)
|
||||
}
|
||||
|
||||
override var message: CharSequence
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setMessage(value)
|
||||
}
|
||||
|
||||
override var messageResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setMessage(value)
|
||||
}
|
||||
|
||||
override var icon: Drawable
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setIcon(value)
|
||||
}
|
||||
|
||||
override var iconResource: Int
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setIcon(value)
|
||||
}
|
||||
|
||||
override var customTitle: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setCustomTitle(value)
|
||||
}
|
||||
|
||||
override var customView: View
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setView(value)
|
||||
}
|
||||
|
||||
override var isCancelable: Boolean
|
||||
@Deprecated(NO_GETTER, level = ERROR) get() = AnkoInternals.noGetter()
|
||||
set(value) {
|
||||
builder.setCancelable(value)
|
||||
}
|
||||
|
||||
override fun onCancelled(handler: (DialogInterface) -> Unit) {
|
||||
builder.setOnCancelListener(handler)
|
||||
}
|
||||
|
||||
override fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) {
|
||||
builder.setOnKeyListener(handler)
|
||||
}
|
||||
|
||||
override fun positiveButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setPositiveButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun positiveButton(
|
||||
buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
) {
|
||||
builder.setPositiveButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun negativeButton(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNegativeButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun negativeButton(
|
||||
buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
) {
|
||||
builder.setNegativeButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun neutralPressed(buttonText: String, onClicked: (dialog: DialogInterface) -> Unit) {
|
||||
builder.setNeutralButton(buttonText) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun neutralPressed(
|
||||
buttonTextResource: Int,
|
||||
onClicked: (dialog: DialogInterface) -> Unit,
|
||||
) {
|
||||
builder.setNeutralButton(buttonTextResource) { dialog, _ -> onClicked(dialog) }
|
||||
}
|
||||
|
||||
override fun items(
|
||||
items: List<CharSequence>,
|
||||
onItemSelected: (dialog: DialogInterface, index: Int) -> Unit,
|
||||
) {
|
||||
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
|
||||
onItemSelected(dialog, which)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> items(
|
||||
items: List<T>,
|
||||
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit,
|
||||
) {
|
||||
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
|
||||
onItemSelected(dialog, items[which], which)
|
||||
}
|
||||
}
|
||||
|
||||
override fun build(): AlertDialog = builder.create()
|
||||
|
||||
override fun show(): AlertDialog = builder.show()
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.ClickableSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.text.style.UnderlineSpan
|
||||
import android.view.View
|
||||
|
||||
inline fun buildSpanned(f: SpannableStringBuilder.() -> Unit): Spanned =
|
||||
SpannableStringBuilder().apply(f)
|
||||
|
||||
inline val SpannableStringBuilder.Bold: StyleSpan
|
||||
get() = StyleSpan(Typeface.BOLD)
|
||||
|
||||
inline val SpannableStringBuilder.Italic: StyleSpan
|
||||
get() = StyleSpan(Typeface.ITALIC)
|
||||
|
||||
inline val SpannableStringBuilder.Underline: UnderlineSpan
|
||||
get() = UnderlineSpan()
|
||||
|
||||
inline val SpannableStringBuilder.Strikethrough: StrikethroughSpan
|
||||
get() = StrikethroughSpan()
|
||||
|
||||
inline fun SpannableStringBuilder.foregroundColor(color: Int): ForegroundColorSpan =
|
||||
ForegroundColorSpan(color)
|
||||
|
||||
inline fun SpannableStringBuilder.backgroundColor(color: Int): BackgroundColorSpan =
|
||||
BackgroundColorSpan(color)
|
||||
|
||||
inline fun SpannableStringBuilder.clickable(crossinline onClick: (View) -> Unit): ClickableSpan {
|
||||
return object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
onClick(widget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun SpannableStringBuilder.link(url: String): URLSpan {
|
||||
return URLSpan(url)
|
||||
}
|
||||
|
||||
fun SpannableStringBuilder.append(text: CharSequence, vararg spans: Any) {
|
||||
val textLength = text.length
|
||||
append(text)
|
||||
spans.forEach { span ->
|
||||
setSpan(span, this.length - textLength, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
fun SpannableStringBuilder.append(text: CharSequence, span: Any) {
|
||||
val textLength = text.length
|
||||
append(text)
|
||||
setSpan(span, this.length - textLength, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
|
||||
inline fun SpannableStringBuilder.append(span: Any, f: SpannableStringBuilder.() -> Unit) = apply {
|
||||
val start = length
|
||||
f()
|
||||
setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
|
||||
inline fun SpannableStringBuilder.appendln(text: CharSequence, vararg spans: Any) {
|
||||
append(text, *spans)
|
||||
appendln()
|
||||
}
|
||||
|
||||
inline fun SpannableStringBuilder.appendln(text: CharSequence, span: Any) {
|
||||
append(text, span)
|
||||
appendln()
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package org.jetbrains.anko.collections
|
||||
|
||||
/**
|
||||
* Iterate the receiver [Array] using an index.
|
||||
*
|
||||
* @f an action to invoke on each array element.
|
||||
*/
|
||||
@Deprecated(message = "Use the native Kotlin version", replaceWith = ReplaceWith("forEach(f)"))
|
||||
inline fun <T> Array<T>.forEachByIndex(f: (T) -> Unit) {
|
||||
val lastIndex = size - 1
|
||||
for (i in 0..lastIndex) {
|
||||
f(get(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [Array] using an index.
|
||||
*
|
||||
* @f an action to invoke on each array element (index, element).
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use the native Kotlin version",
|
||||
replaceWith = ReplaceWith("forEachIndexed(f)")
|
||||
)
|
||||
inline fun <T> Array<T>.forEachWithIndex(f: (Int, T) -> Unit) {
|
||||
val lastIndex = size - 1
|
||||
for (i in 0..lastIndex) {
|
||||
f(i, get(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [Array] backwards using an index.
|
||||
*
|
||||
* @f an action to invoke on each array element.
|
||||
*/
|
||||
inline fun <T> Array<T>.forEachReversedByIndex(f: (T) -> Unit) {
|
||||
var i = size - 1
|
||||
while (i >= 0) {
|
||||
f(get(i))
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [Array] backwards using an index.
|
||||
*
|
||||
* @f an action to invoke on each array element (index, element).
|
||||
*/
|
||||
inline fun <T> Array<T>.forEachReversedWithIndex(f: (Int, T) -> Unit) {
|
||||
var i = size - 1
|
||||
while (i >= 0) {
|
||||
f(i, get(i))
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * Create a [Sequence] instance that wraps the original [SparseArray] returning its elements when being iterated.
|
||||
// */
|
||||
//inline fun <T> SparseArray<T>.asSequence(): Sequence<T> = SparseArraySequence(this)
|
||||
//
|
||||
///**
|
||||
// * Create a [Sequence] instance that wraps the original [SparseBooleanArray] returning its elements when being iterated.
|
||||
// */
|
||||
//inline fun <T> SparseBooleanArray.asSequence(): Sequence<Boolean> = SparseBooleanArraySequence(this)
|
||||
//
|
||||
///**
|
||||
// * Create a [Sequence] instance that wraps the original [SparseIntArray] returning its elements when being iterated.
|
||||
// */
|
||||
//inline fun <T> SparseIntArray.asSequence(): Sequence<Int> = SparseIntArraySequence(this)
|
||||
//
|
||||
//@PublishedApi
|
||||
//internal class SparseArraySequence<T>(private val a: SparseArray<T>) : Sequence<T> {
|
||||
// override fun iterator(): Iterator<T> = SparseArrayIterator()
|
||||
//
|
||||
// private inner class SparseArrayIterator : Iterator<T> {
|
||||
// private var index = 0
|
||||
// private val size = a.size()
|
||||
//
|
||||
// override fun hasNext() = size > index
|
||||
//
|
||||
// override fun next(): T {
|
||||
// if (a.size() != size) throw ConcurrentModificationException()
|
||||
// return a.valueAt(index++)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@PublishedApi
|
||||
//internal class SparseBooleanArraySequence(private val a: SparseBooleanArray) : Sequence<Boolean> {
|
||||
// override fun iterator(): Iterator<Boolean> = SparseIntArrayIterator()
|
||||
//
|
||||
// private inner class SparseIntArrayIterator : Iterator<Boolean> {
|
||||
// private var index = 0
|
||||
// private val size = a.size()
|
||||
//
|
||||
// override fun hasNext() = size > index
|
||||
//
|
||||
// override fun next(): Boolean {
|
||||
// if (a.size() != size) throw ConcurrentModificationException()
|
||||
// return a.get(index++)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@PublishedApi
|
||||
//internal class SparseIntArraySequence(private val a: SparseIntArray) : Sequence<Int> {
|
||||
// override fun iterator(): Iterator<Int> = SparseIntArrayIterator()
|
||||
//
|
||||
// private inner class SparseIntArrayIterator : Iterator<Int> {
|
||||
// private var index = 0
|
||||
// private val size = a.size()
|
||||
//
|
||||
// override fun hasNext() = size > index
|
||||
//
|
||||
// override fun next(): Int {
|
||||
// if (a.size() != size) throw ConcurrentModificationException()
|
||||
// return a.get(index++)
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
package org.jetbrains.anko.collections
|
||||
|
||||
/**
|
||||
* Iterate the receiver [List] using an index.
|
||||
*
|
||||
* @f an action to invoke on each list element.
|
||||
*/
|
||||
inline fun <T> List<T>.forEachByIndex(f: (T) -> Unit) {
|
||||
val lastIndex = size - 1
|
||||
for (i in 0..lastIndex) {
|
||||
f(get(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [List] using an index.
|
||||
*
|
||||
* @f an action to invoke on each list element (index, element).
|
||||
*/
|
||||
inline fun <T> List<T>.forEachWithIndex(f: (Int, T) -> Unit) {
|
||||
val lastIndex = size - 1
|
||||
for (i in 0..lastIndex) {
|
||||
f(i, get(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [List] backwards using an index.
|
||||
*
|
||||
* @f an action to invoke on each list element.
|
||||
*/
|
||||
inline fun <T> List<T>.forEachReversedByIndex(f: (T) -> Unit) {
|
||||
var i = size - 1
|
||||
while (i >= 0) {
|
||||
f(get(i))
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [List] backwards using an index.
|
||||
*
|
||||
* @f an action to invoke on each list element (index, element).
|
||||
*/
|
||||
inline fun <T> List<T>.forEachReversedWithIndex(f: (Int, T) -> Unit) {
|
||||
var i = size - 1
|
||||
while (i >= 0) {
|
||||
f(i, get(i))
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the Android pair to a Kotlin one.
|
||||
*
|
||||
* @see [toAndroidPair].
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("toKotlinPair()", "androidx.core.util.toKotlinPair"))
|
||||
inline fun <F, S> android.util.Pair<F, S>.toKotlinPair(): Pair<F, S> = first to second
|
||||
|
||||
/**
|
||||
* Convert the Kotlin pair to an Android one.
|
||||
*
|
||||
* @see [toKotlinPair].
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("toAndroidPair()", "androidx.core.util.toAndroidPair"))
|
||||
inline fun <F, S> Pair<F, S>.toAndroidPair(): android.util.Pair<F, S> = android.util.Pair(first, second)
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.anko.collections
|
||||
|
||||
import android.util.SparseArray
|
||||
import android.util.SparseBooleanArray
|
||||
import android.util.SparseIntArray
|
||||
import java.util.ConcurrentModificationException
|
||||
|
||||
/**
|
||||
* Iterate the receiver [SparseArray]
|
||||
* @action an action to invoke on each key value pair
|
||||
* @throws [ConcurrentModificationException] if modified while iterating
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
|
||||
inline fun <E> SparseArray<E>.forEach(action: (Int, E) -> Unit) {
|
||||
val size = this.size()
|
||||
for (i in 0..size - 1) {
|
||||
if (size != this.size()) throw ConcurrentModificationException()
|
||||
action(this.keyAt(i), this.valueAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [SparseBooleanArray]
|
||||
* @action an action to invoke on each key value pair
|
||||
* @throws [ConcurrentModificationException] if modified while iterating
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
|
||||
inline fun SparseBooleanArray.forEach(action: (Int, Boolean) -> Unit) {
|
||||
val size = this.size()
|
||||
for (i in 0..size - 1) {
|
||||
if (size != this.size()) throw ConcurrentModificationException()
|
||||
action(this.keyAt(i), this.valueAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate the receiver [SparseIntArray]
|
||||
* @action an action to invoke on each key value pair
|
||||
* @throws [ConcurrentModificationException] if modified while iterating
|
||||
*/
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("forEach(action)", "androidx.core.util.forEach"))
|
||||
inline fun SparseIntArray.forEach(action: (Int, Int) -> Unit) {
|
||||
val size = this.size()
|
||||
for (i in 0..size - 1) {
|
||||
if (size != this.size()) throw ConcurrentModificationException()
|
||||
action(this.keyAt(i), this.valueAt(i))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko.custom
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewManager
|
||||
import org.jetbrains.anko.internals.AnkoInternals
|
||||
|
||||
inline fun <T : View> ViewManager.ankoView(
|
||||
factory: (ctx: Context) -> T,
|
||||
theme: Int,
|
||||
init: T.() -> Unit,
|
||||
): T {
|
||||
val ctx = AnkoInternals.wrapContextIfNeeded(AnkoInternals.getContext(this), theme)
|
||||
val view = factory(ctx)
|
||||
view.init()
|
||||
AnkoInternals.addView(this, view)
|
||||
return view
|
||||
}
|
||||
|
||||
inline fun <T : View> Context.ankoView(
|
||||
factory: (ctx: Context) -> T,
|
||||
theme: Int,
|
||||
init: T.() -> Unit,
|
||||
): T {
|
||||
val ctx = AnkoInternals.wrapContextIfNeeded(this, theme)
|
||||
val view = factory(ctx)
|
||||
view.init()
|
||||
AnkoInternals.addView(this, view)
|
||||
return view
|
||||
}
|
||||
|
||||
inline fun <T : View> Activity.ankoView(
|
||||
factory: (ctx: Context) -> T,
|
||||
theme: Int,
|
||||
init: T.() -> Unit,
|
||||
): T {
|
||||
val ctx = AnkoInternals.wrapContextIfNeeded(this, theme)
|
||||
val view = factory(ctx)
|
||||
view.init()
|
||||
AnkoInternals.addView(this, view)
|
||||
return view
|
||||
}
|
||||
|
||||
inline fun <reified T : View> ViewManager.customView(theme: Int = 0, init: T.() -> Unit): T =
|
||||
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }
|
||||
|
||||
inline fun <reified T : View> Context.customView(theme: Int = 0, init: T.() -> Unit): T =
|
||||
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }
|
||||
|
||||
inline fun <reified T : View> Activity.customView(theme: Int = 0, init: T.() -> Unit): T =
|
||||
ankoView({ ctx -> AnkoInternals.initiateView(ctx, T::class.java) }, theme) { init() }
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko.custom
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.jetbrains.anko.AnkoAsyncContext
|
||||
import org.jetbrains.anko.BackgroundExecutor
|
||||
import org.jetbrains.anko.applyRecursively
|
||||
import org.jetbrains.anko.collections.forEachReversedByIndex
|
||||
import org.jetbrains.anko.runOnUiThread
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Future
|
||||
|
||||
@Deprecated(
|
||||
"Use forEachReversedByIndex(f) instead.",
|
||||
ReplaceWith(
|
||||
"forEachReversedByIndex(f)",
|
||||
"org.jetbrains.anko.collections.forEachReversedByIndex"
|
||||
)
|
||||
)
|
||||
inline fun <T> Array<T>.forEachReversed(f: (T) -> Unit) = forEachReversedByIndex(f)
|
||||
|
||||
@Deprecated(
|
||||
"Use forEachReversedByIndex(f) instead.",
|
||||
ReplaceWith(
|
||||
"forEachReversedByIndex(f)",
|
||||
"org.jetbrains.anko.collections.forEachReversedByIndex"
|
||||
)
|
||||
)
|
||||
inline fun <T> List<T>.forEachReversed(f: (T) -> Unit) = forEachReversedByIndex(f)
|
||||
|
||||
@Deprecated("Use runOnUiThread(f) instead.", ReplaceWith("runOnUiThread(f)"))
|
||||
inline fun Fragment.onUiThread(crossinline f: () -> Unit) = runOnUiThread(f)
|
||||
|
||||
@Deprecated("Use runOnUiThread(f) instead.", ReplaceWith("runOnUiThread(f)"))
|
||||
fun Context.onUiThread(f: Context.() -> Unit) = runOnUiThread(f)
|
||||
|
||||
@Deprecated("Use doAsync(task) instead.", ReplaceWith("doAsync(task)"))
|
||||
fun <T> T.async(task: AnkoAsyncContext<T>.() -> Unit): Future<Unit> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return BackgroundExecutor.submit { context.task() }
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use doAsync(executorService, task) instead.",
|
||||
ReplaceWith("doAsync(executorService, task)")
|
||||
)
|
||||
fun <T> T.async(
|
||||
executorService: ExecutorService,
|
||||
task: AnkoAsyncContext<T>.() -> Unit,
|
||||
): Future<Unit> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return executorService.submit<Unit> { context.task() }
|
||||
}
|
||||
|
||||
@Deprecated("Use doAsyncResult(task) instead.", ReplaceWith("doAsyncResult(task)"))
|
||||
fun <T, R> T.asyncResult(task: AnkoAsyncContext<T>.() -> R): Future<R> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return BackgroundExecutor.submit { context.task() }
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"Use doAsyncResult(executorService, task) instead.",
|
||||
ReplaceWith("doAsyncResult(executorService, task)")
|
||||
)
|
||||
fun <T, R> T.asyncResult(
|
||||
executorService: ExecutorService,
|
||||
task: AnkoAsyncContext<T>.() -> R,
|
||||
): Future<R> {
|
||||
val context = AnkoAsyncContext(WeakReference(this))
|
||||
return executorService.submit<R> { context.task() }
|
||||
}
|
||||
|
||||
@Deprecated("Use applyRecursively(block) instead.", ReplaceWith("applyRecursively(style)"))
|
||||
fun <T : View> T.style(style: (View) -> Unit): T = applyRecursively(style)
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("MatchingDeclarationName")
|
||||
|
||||
package org.jetbrains.anko.internals
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Service
|
||||
import android.app.UiModeManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.database.Cursor
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewManager
|
||||
import org.jetbrains.anko.*
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
object AnkoInternals {
|
||||
const val NO_GETTER: String = "Property does not have a getter"
|
||||
|
||||
fun noGetter(): Nothing = throw AnkoException("Property does not have a getter")
|
||||
|
||||
private class AnkoContextThemeWrapper(base: Context?, val theme: Int) :
|
||||
ContextThemeWrapper(base, theme)
|
||||
|
||||
fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
|
||||
is ViewGroup -> manager.addView(view)
|
||||
is AnkoContext<*> -> manager.addView(view, null)
|
||||
else -> throw AnkoException("$manager is the wrong parent")
|
||||
}
|
||||
|
||||
fun <T : View> addView(ctx: Context, view: T) {
|
||||
ctx.UI { addView(this, view) }
|
||||
}
|
||||
|
||||
fun <T : View> addView(activity: Activity, view: T) {
|
||||
createAnkoContext(activity, { addView(this, view) }, true)
|
||||
}
|
||||
|
||||
fun wrapContextIfNeeded(ctx: Context, theme: Int): Context {
|
||||
return if (theme != 0 && (ctx !is AnkoContextThemeWrapper || ctx.theme != theme)) {
|
||||
// If the context isn't a ContextThemeWrapper, or it is but does not have
|
||||
// the same theme as we need, wrap it in a new wrapper
|
||||
AnkoContextThemeWrapper(ctx, theme)
|
||||
} else {
|
||||
ctx
|
||||
}
|
||||
}
|
||||
|
||||
fun applyRecursively(v: View, style: (View) -> Unit) {
|
||||
style(v)
|
||||
if (v is ViewGroup) {
|
||||
val maxIndex = v.childCount - 1
|
||||
for (i in 0..maxIndex) {
|
||||
v.getChildAt(i)?.let { applyRecursively(it, style) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getContext(manager: ViewManager): Context = when (manager) {
|
||||
is ViewGroup -> manager.context
|
||||
is AnkoContext<*> -> manager.ctx
|
||||
else -> throw AnkoException("$manager is the wrong parent")
|
||||
}
|
||||
|
||||
inline fun <T> T.createAnkoContext(
|
||||
ctx: Context,
|
||||
init: AnkoContext<T>.() -> Unit,
|
||||
setContentView: Boolean = false,
|
||||
): AnkoContext<T> {
|
||||
val dsl = AnkoContextImpl(ctx, this, setContentView)
|
||||
dsl.init()
|
||||
return dsl
|
||||
}
|
||||
|
||||
// Some constants not present in Android SDK v.15
|
||||
private object InternalConfiguration {
|
||||
const val SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0
|
||||
const val SCREENLAYOUT_LAYOUTDIR_SHIFT = 6
|
||||
const val SCREENLAYOUT_LAYOUTDIR_RTL = 0x02 shl SCREENLAYOUT_LAYOUTDIR_SHIFT
|
||||
|
||||
const val UI_MODE_TYPE_APPLIANCE = 0x05
|
||||
const val UI_MODE_TYPE_WATCH = 0x06
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <T> createIntent(
|
||||
ctx: Context,
|
||||
clazz: Class<out T>,
|
||||
params: Array<out Pair<String, Any?>>,
|
||||
): Intent {
|
||||
val intent = Intent(ctx, clazz)
|
||||
if (params.isNotEmpty()) fillIntentArguments(intent, params)
|
||||
return intent
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun internalStartActivity(
|
||||
ctx: Context,
|
||||
activity: Class<out Activity>,
|
||||
params: Array<out Pair<String, Any?>>,
|
||||
) {
|
||||
ctx.startActivity(createIntent(ctx, activity, params))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun internalStartActivityForResult(
|
||||
act: Activity,
|
||||
activity: Class<out Activity>,
|
||||
requestCode: Int,
|
||||
params: Array<out Pair<String, Any?>>,
|
||||
) {
|
||||
act.startActivityForResult(createIntent(act, activity, params), requestCode)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun internalStartService(
|
||||
ctx: Context,
|
||||
service: Class<out Service>,
|
||||
params: Array<out Pair<String, Any?>>,
|
||||
): ComponentName? = ctx.startService(createIntent(ctx, service, params))
|
||||
|
||||
@JvmStatic
|
||||
fun internalStopService(
|
||||
ctx: Context,
|
||||
service: Class<out Service>,
|
||||
params: Array<out Pair<String, Any?>>,
|
||||
): Boolean = ctx.stopService(createIntent(ctx, service, params))
|
||||
|
||||
@JvmStatic
|
||||
private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
|
||||
params.forEach {
|
||||
when (val value = it.second) {
|
||||
null -> intent.putExtra(it.first, null as Serializable?)
|
||||
is Int -> intent.putExtra(it.first, value)
|
||||
is Long -> intent.putExtra(it.first, value)
|
||||
is String -> intent.putExtra(it.first, value)
|
||||
is Float -> intent.putExtra(it.first, value)
|
||||
is Double -> intent.putExtra(it.first, value)
|
||||
is Char -> intent.putExtra(it.first, value)
|
||||
is Short -> intent.putExtra(it.first, value)
|
||||
is Boolean -> intent.putExtra(it.first, value)
|
||||
is Bundle -> intent.putExtra(it.first, value)
|
||||
is Array<*> -> when {
|
||||
value.isArrayOf<CharSequence>() -> intent.putExtra(it.first, value)
|
||||
value.isArrayOf<String>() -> intent.putExtra(it.first, value)
|
||||
value.isArrayOf<Parcelable>() -> intent.putExtra(it.first, value)
|
||||
else -> throw AnkoException("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
|
||||
}
|
||||
is IntArray -> intent.putExtra(it.first, value)
|
||||
is LongArray -> intent.putExtra(it.first, value)
|
||||
is FloatArray -> intent.putExtra(it.first, value)
|
||||
is DoubleArray -> intent.putExtra(it.first, value)
|
||||
is CharArray -> intent.putExtra(it.first, value)
|
||||
is ShortArray -> intent.putExtra(it.first, value)
|
||||
is BooleanArray -> intent.putExtra(it.first, value)
|
||||
is CharSequence -> intent.putExtra(it.first, value)
|
||||
is Serializable -> intent.putExtra(it.first, value)
|
||||
is Parcelable -> intent.putExtra(it.first, value)
|
||||
else -> throw AnkoException("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
inline fun <T> useCursor(cursor: Cursor, f: (Cursor) -> T): T {
|
||||
return cursor.use(f)
|
||||
// Closeable only added in API 16
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <T : View> initiateView(ctx: Context, viewClass: Class<T>): T {
|
||||
fun getConstructor1() = viewClass.getConstructor(Context::class.java)
|
||||
fun getConstructor2() =
|
||||
viewClass.getConstructor(Context::class.java, AttributeSet::class.java)
|
||||
|
||||
return try {
|
||||
getConstructor1().newInstance(ctx)
|
||||
} catch (_: NoSuchMethodException) {
|
||||
try {
|
||||
getConstructor2().newInstance(ctx, null)
|
||||
} catch (_: NoSuchMethodException) {
|
||||
throw AnkoException("Can't initiate View of class ${viewClass.name}: can't find proper constructor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@JvmStatic
|
||||
fun testConfiguration(
|
||||
ctx: Context,
|
||||
screenSize: ScreenSize?,
|
||||
density: ClosedRange<Int>?,
|
||||
language: String?,
|
||||
orientation: Orientation?,
|
||||
long: Boolean?,
|
||||
fromSdk: Int?,
|
||||
sdk: Int?,
|
||||
uiMode: UiMode?,
|
||||
nightMode: Boolean?,
|
||||
rightToLeft: Boolean?,
|
||||
smallestWidth: Int?,
|
||||
): Boolean {
|
||||
val config = ctx.resources?.configuration
|
||||
|
||||
if (screenSize != null) {
|
||||
if (config == null) return false
|
||||
when (config.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) {
|
||||
Configuration.SCREENLAYOUT_SIZE_UNDEFINED -> {}
|
||||
Configuration.SCREENLAYOUT_SIZE_SMALL -> if (screenSize != ScreenSize.SMALL) return false
|
||||
Configuration.SCREENLAYOUT_SIZE_NORMAL -> if (screenSize != ScreenSize.NORMAL) return false
|
||||
Configuration.SCREENLAYOUT_SIZE_LARGE -> if (screenSize != ScreenSize.LARGE) return false
|
||||
Configuration.SCREENLAYOUT_SIZE_XLARGE -> if (screenSize != ScreenSize.XLARGE) return false
|
||||
}
|
||||
}
|
||||
|
||||
if (density != null) {
|
||||
val currentDensityDpi = ctx.resources?.displayMetrics?.densityDpi ?: return false
|
||||
if (currentDensityDpi !in density || currentDensityDpi == density.endInclusive) return false
|
||||
}
|
||||
|
||||
if (language != null) {
|
||||
val locale = Locale.getDefault()
|
||||
val currentLanguage =
|
||||
if (language.indexOf('_') >= 0) locale.toString() else locale.language
|
||||
if (currentLanguage != language) return false
|
||||
}
|
||||
|
||||
if (orientation != null) {
|
||||
if (config == null) return false
|
||||
@Suppress("DEPRECATION")
|
||||
when (config.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> if (orientation != Orientation.LANDSCAPE) return false
|
||||
Configuration.ORIENTATION_PORTRAIT -> if (orientation != Orientation.PORTRAIT) return false
|
||||
Configuration.ORIENTATION_SQUARE -> if (orientation != Orientation.SQUARE) return false
|
||||
Configuration.ORIENTATION_UNDEFINED -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
if (long != null) {
|
||||
if (config == null) return false
|
||||
val currentLong = config.screenLayout and Configuration.SCREENLAYOUT_LONG_MASK
|
||||
if (currentLong == Configuration.SCREENLAYOUT_LONG_YES && !long) return false
|
||||
if (currentLong == Configuration.SCREENLAYOUT_LONG_NO && long) return false
|
||||
}
|
||||
|
||||
if (fromSdk != null) {
|
||||
if (Build.VERSION.SDK_INT < fromSdk) return false
|
||||
}
|
||||
|
||||
if (sdk != null) {
|
||||
if (Build.VERSION.SDK_INT != sdk) return false
|
||||
}
|
||||
|
||||
if (uiMode != null) {
|
||||
if (config == null) return false
|
||||
when (config.uiMode and Configuration.UI_MODE_TYPE_MASK) {
|
||||
Configuration.UI_MODE_TYPE_NORMAL -> if (uiMode != UiMode.NORMAL) return false
|
||||
Configuration.UI_MODE_TYPE_DESK -> if (uiMode != UiMode.DESK) return false
|
||||
Configuration.UI_MODE_TYPE_CAR -> if (uiMode != UiMode.CAR) return false
|
||||
Configuration.UI_MODE_TYPE_TELEVISION -> if (uiMode != UiMode.TELEVISION) return false
|
||||
InternalConfiguration.UI_MODE_TYPE_APPLIANCE -> if (uiMode != UiMode.APPLIANCE) return false
|
||||
InternalConfiguration.UI_MODE_TYPE_WATCH -> if (uiMode != UiMode.WATCH) return false
|
||||
}
|
||||
}
|
||||
|
||||
if (nightMode != null) {
|
||||
val uiModeManager =
|
||||
ctx.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager ?: return false
|
||||
|
||||
val currentMode = uiModeManager.nightMode
|
||||
if (currentMode == UiModeManager.MODE_NIGHT_YES && !nightMode) return false
|
||||
if (currentMode == UiModeManager.MODE_NIGHT_NO && nightMode) return false
|
||||
}
|
||||
|
||||
if (rightToLeft != null) {
|
||||
if (config == null) return false
|
||||
val rtlMode = (config.screenLayout and
|
||||
InternalConfiguration.SCREENLAYOUT_LAYOUTDIR_MASK) == InternalConfiguration.SCREENLAYOUT_LAYOUTDIR_RTL
|
||||
if (rtlMode != rightToLeft) return false
|
||||
}
|
||||
|
||||
if (smallestWidth != null) {
|
||||
if (config == null) return false
|
||||
|
||||
if (config.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
|
||||
if (smallestWidth != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) return false
|
||||
} else if (config.smallestScreenWidthDp < smallestWidth) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import java.util.ConcurrentModificationException
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
@Deprecated(message = "Use the Android KTX version", replaceWith = ReplaceWith("children", "androidx.core.view.children"))
|
||||
fun Menu.itemsSequence(): Sequence<MenuItem> = MenuItemsSequence(this)
|
||||
|
||||
private class MenuItemsSequence(private val menu: Menu) : Sequence<MenuItem> {
|
||||
override fun iterator(): Iterator<MenuItem> = MenuItemIterator(menu)
|
||||
|
||||
private class MenuItemIterator(private val menu: Menu) : Iterator<MenuItem> {
|
||||
private var index = 0
|
||||
private val count = menu.size()
|
||||
|
||||
override fun next(): MenuItem {
|
||||
if (!hasNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
return menu.getItem(index++)
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (count != menu.size()) {
|
||||
throw ConcurrentModificationException()
|
||||
}
|
||||
|
||||
return index < count
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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:Suppress("unused")
|
||||
|
||||
package org.jetbrains.anko
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
/**
|
||||
* Execute [action] for each child of the received [ViewGroup].
|
||||
*
|
||||
* @param action the action to execute.
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use the Android KTX version",
|
||||
replaceWith = ReplaceWith("forEach(action)", "androidx.core.view.forEach")
|
||||
)
|
||||
inline fun ViewGroup.forEachChild(action: (View) -> Unit) {
|
||||
for (i in 0..childCount - 1) {
|
||||
action(getChildAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [action] for each child of the received [ViewGroup].
|
||||
*
|
||||
* @param action the action to execute. The first index is 0.
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use the Android KTX version",
|
||||
replaceWith = ReplaceWith("forEachIndexed(action)", "androidx.core.view.forEachIndexed")
|
||||
)
|
||||
inline fun ViewGroup.forEachChildWithIndex(action: (Int, View) -> Unit) {
|
||||
for (i in 0..childCount - 1) {
|
||||
action(i, getChildAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first child [View] matching the given [predicate].
|
||||
*
|
||||
* @param predicate the predicate to check against.
|
||||
* @return the child [View] that matches [predicate].
|
||||
* [NoSuchElementException] will be thrown if no such child was found.
|
||||
*/
|
||||
inline fun ViewGroup.firstChild(predicate: (View) -> Boolean): View {
|
||||
return firstChildOrNull(predicate)
|
||||
?: throw NoSuchElementException("No element matching predicate was found.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first child [View] matching the given [predicate].
|
||||
*
|
||||
* @param predicate the predicate to check against.
|
||||
* @return the child [View] that matches [predicate], or null if no such child was found.
|
||||
*/
|
||||
inline fun ViewGroup.firstChildOrNull(predicate: (View) -> Boolean): View? {
|
||||
for (i in 0..childCount - 1) {
|
||||
val child = getChildAt(i)
|
||||
if (predicate(child)) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sequence of children of the received [View].
|
||||
* Note that the sequence is not thread-safe.
|
||||
*
|
||||
* @return the [Sequence] of children.
|
||||
*/
|
||||
@Deprecated(
|
||||
message = "Use the Android KTX version",
|
||||
replaceWith = ReplaceWith("children", "androidx.core.view.children")
|
||||
)
|
||||
fun View.childrenSequence(): Sequence<View> = ViewChildrenSequence(this)
|
||||
|
||||
/**
|
||||
* Return the [Sequence] of all children of the received [View], recursively.
|
||||
* Note that the sequence is not thread-safe.
|
||||
*
|
||||
* @return the [Sequence] of children.
|
||||
*/
|
||||
fun View.childrenRecursiveSequence(): Sequence<View> = ViewChildrenRecursiveSequence(this)
|
||||
|
||||
private class ViewChildrenSequence(private val view: View) : Sequence<View> {
|
||||
override fun iterator(): Iterator<View> {
|
||||
if (view !is ViewGroup) return emptyList<View>().iterator()
|
||||
return ViewIterator(view)
|
||||
}
|
||||
|
||||
private class ViewIterator(private val view: ViewGroup) : Iterator<View> {
|
||||
private var index = 0
|
||||
private val count = view.childCount
|
||||
|
||||
override fun next(): View {
|
||||
if (!hasNext()) throw NoSuchElementException()
|
||||
return view.getChildAt(index++)
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
checkCount()
|
||||
return index < count
|
||||
}
|
||||
|
||||
private fun checkCount() {
|
||||
if (count != view.childCount) throw ConcurrentModificationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewChildrenRecursiveSequence(private val view: View) : Sequence<View> {
|
||||
override fun iterator(): Iterator<View> {
|
||||
if (view !is ViewGroup) return emptyList<View>().iterator()
|
||||
return RecursiveViewIterator(view)
|
||||
}
|
||||
|
||||
private class RecursiveViewIterator(view: View) : Iterator<View> {
|
||||
private val sequences = arrayListOf(view.childrenSequence())
|
||||
private var current = sequences.removeLast().iterator()
|
||||
|
||||
override fun next(): View {
|
||||
if (!hasNext()) throw NoSuchElementException()
|
||||
val view = current.next()
|
||||
if (view is ViewGroup && view.childCount > 0) {
|
||||
sequences.add(view.childrenSequence())
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (!current.hasNext() && sequences.isNotEmpty()) {
|
||||
current = sequences.removeLast().iterator()
|
||||
}
|
||||
return current.hasNext()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun <T : Any> MutableList<T>.removeLast(): T {
|
||||
if (isEmpty()) throw NoSuchElementException()
|
||||
return removeAt(size - 1)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package jp.juggler.anko
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ compileKotlin {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
freeCompilerArgs += [
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
]
|
||||
|
@ -20,8 +20,8 @@ compileKotlin {
|
|||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
//noinspection DifferentStdlibGradleVersion
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib"
|
||||
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
|
||||
}
|
||||
|
|
|
@ -2,19 +2,19 @@ apply plugin: "com.android.library"
|
|||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
namespace 'jp.juggler.apng'
|
||||
namespace "jp.juggler.apng"
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
targetSdkVersion stTargetSdkVersion
|
||||
minSdkVersion stMinSdkVersion
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
freeCompilerArgs += [
|
||||
// "-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
// "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
|
@ -47,5 +47,5 @@ dependencies {
|
|||
implementation project(":base")
|
||||
|
||||
implementation "com.github.zjupure:webpdecoder:2.3.$glideVersion"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ apply plugin: "io.gitlab.arturbosch.detekt"
|
|||
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
// exoPlayer 2.9.0 以降は Java 8 compiler support を要求する
|
||||
compileOptions {
|
||||
|
@ -22,8 +22,8 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
targetSdkVersion stTargetSdkVersion
|
||||
minSdkVersion stMinSdkVersion
|
||||
|
||||
versionCode 511
|
||||
versionName "5.511"
|
||||
|
@ -38,7 +38,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
freeCompilerArgs += [
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
|
@ -128,39 +128,39 @@ kapt {
|
|||
dependencies {
|
||||
// desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する
|
||||
//noinspection GradleDependency
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||
|
||||
implementation(project(":base"))
|
||||
implementation project(':colorpicker')
|
||||
implementation project(':emoji')
|
||||
implementation project(':apng_android')
|
||||
implementation project(':anko')
|
||||
implementation fileTree(include: ['*.aar'], dir: 'src/main/libs')
|
||||
|
||||
// App1 とサーバ情報カラムで使う
|
||||
api "org.conscrypt:conscrypt-android:2.5.2"
|
||||
implementation "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
|
||||
|
||||
kapt "androidx.annotation:annotation:1.5.0"
|
||||
kapt "androidx.annotation:annotation:$androidxAnnotationVersion"
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
kapt "com.github.bumptech.glide:compiler:$glideVersion"
|
||||
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version")
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||
|
||||
testImplementation(project(":base"))
|
||||
androidTestImplementation(project(":base"))
|
||||
|
||||
androidTestApi "androidx.test.espresso:espresso-core:3.5.1"
|
||||
androidTestApi "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
|
||||
androidTestApi "androidx.test.ext:junit-ktx:1.1.5"
|
||||
androidTestApi "androidx.test.ext:junit:1.1.5"
|
||||
androidTestApi "androidx.test.ext:junit:$androidxTestExtJunitVersion"
|
||||
androidTestApi "androidx.test.ext:truth:1.5.0"
|
||||
androidTestApi "androidx.test:core-ktx:1.5.0"
|
||||
androidTestApi "androidx.test:core:$androidx_test_version"
|
||||
androidTestApi "androidx.test:core-ktx:$testKtxVersion"
|
||||
androidTestApi "androidx.test:core:$androidxTestVersion"
|
||||
androidTestApi "androidx.test:runner:1.5.2"
|
||||
androidTestApi "org.jetbrains.kotlin:kotlin-test"
|
||||
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
testApi "androidx.arch.core:core-testing:$arch_version"
|
||||
testApi "junit:junit:$junit_version"
|
||||
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
|
||||
testApi "androidx.arch.core:core-testing:$archVersion"
|
||||
testApi "junit:junit:$junitVersion"
|
||||
testApi "org.jetbrains.kotlin:kotlin-test"
|
||||
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
|
||||
|
||||
// To use android test orchestrator
|
||||
androidTestUtil "androidx.test:orchestrator:1.4.2"
|
||||
|
@ -211,6 +211,7 @@ tasks.register("detektAll", Detekt) {
|
|||
}
|
||||
|
||||
source = files(
|
||||
"$rootDir/anko/src",
|
||||
"$rootDir/apng/src",
|
||||
"$rootDir/apng_android/src",
|
||||
"$rootDir/app/src",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -12,11 +12,11 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace "jp.juggler.base"
|
||||
compileSdk compile_sdk_version
|
||||
compileSdk stCompileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk min_sdk_version
|
||||
targetSdk target_sdk_version
|
||||
minSdk stMinSdkVersion
|
||||
targetSdk stTargetSdkVersion
|
||||
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
@ -36,14 +36,14 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = kotlinJvmTarget
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
excludes += ['META-INF/LICENSE*']
|
||||
excludes += ["META-INF/LICENSE*"]
|
||||
}
|
||||
resources {
|
||||
excludes += ['META-INF/LICENSE*']
|
||||
excludes += ["META-INF/LICENSE*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,31 +51,31 @@ android {
|
|||
dependencies {
|
||||
// desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する
|
||||
//noinspection GradleDependency
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||
|
||||
def emoji2Version = "1.2.0"
|
||||
|
||||
api "androidx.appcompat:appcompat:$appcompat_version"
|
||||
api "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
api "androidx.browser:browser:1.4.0"
|
||||
api "androidx.core:core-ktx:1.9.0"
|
||||
api "androidx.core:core-ktx:$coreKtxVersion"
|
||||
api "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
api "androidx.emoji2:emoji2-bundled:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views-helper:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2-views:$emoji2Version"
|
||||
api "androidx.emoji2:emoji2:$emoji2Version"
|
||||
api "androidx.exifinterface:exifinterface:1.3.5"
|
||||
api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-service:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
api "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycleVersion"
|
||||
api "androidx.recyclerview:recyclerview:1.2.1"
|
||||
api "androidx.room:room-ktx:$roomVersion"
|
||||
api "androidx.room:room-runtime:$roomVersion"
|
||||
api "androidx.startup:startup-runtime:$startup_version"
|
||||
api "androidx.startup:startup-runtime:$startupVersion"
|
||||
api "androidx.work:work-runtime-ktx:$workVersion"
|
||||
api "androidx.work:work-runtime:$workVersion"
|
||||
api "com.astuetz:pagerslidingtabstrip:1.0.1"
|
||||
|
@ -86,26 +86,29 @@ dependencies {
|
|||
api "com.github.woxthebox:draglistview:1.6.6"
|
||||
api "com.google.android.exoplayer:exoplayer:2.18.2"
|
||||
api "com.google.android.flexbox:flexbox:3.0.0"
|
||||
api "com.google.android.material:material:1.7.0"
|
||||
api "com.google.android.material:material:$materialVersion"
|
||||
api "com.google.firebase:firebase-messaging:23.1.1"
|
||||
api "com.otaliastudios:transcoder:0.10.4"
|
||||
api "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
|
||||
api "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||
api "commons-codec:commons-codec:1.15"
|
||||
api "io.github.inflationx:calligraphy3:3.1.1"
|
||||
api "io.github.inflationx:viewpump:2.0.3"
|
||||
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
|
||||
api "org.bouncycastle:bcpkix-jdk15on:1.70"
|
||||
api "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesVersion"
|
||||
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
|
||||
api "ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0"
|
||||
|
||||
|
||||
// Koin main features for Android
|
||||
api "io.insert-koin:koin-android:$koin_version"
|
||||
api "io.insert-koin:koin-android-compat:$koin_version"
|
||||
api "io.insert-koin:koin-androidx-workmanager:$koin_version"
|
||||
// api "io.insert-koin:koin-androidx-navigation:$koin_version"
|
||||
// api "io.insert-koin:koin-androidx-compose:$koin_version"
|
||||
api "io.insert-koin:koin-android:$koinVersion"
|
||||
api "io.insert-koin:koin-android-compat:$koinVersion"
|
||||
api "io.insert-koin:koin-androidx-workmanager:$koinVersion"
|
||||
// api "io.insert-koin:koin-androidx-navigation:$koinVersion"
|
||||
// api "io.insert-koin:koin-androidx-compose:$koinVersion"
|
||||
|
||||
api "com.github.bumptech.glide:glide:$glideVersion"
|
||||
api "com.github.bumptech.glide:annotations:$glideVersion"
|
||||
|
@ -113,19 +116,19 @@ dependencies {
|
|||
exclude group: "com.squareup.okhttp3", module: "okhttp"
|
||||
}
|
||||
|
||||
androidTestApi "androidx.test.espresso:espresso-core:3.5.1"
|
||||
androidTestApi "androidx.test.espresso:espresso-core:$androidxTestEspressoCoreVersion"
|
||||
androidTestApi "androidx.test.ext:junit-ktx:1.1.5"
|
||||
androidTestApi "androidx.test.ext:junit:1.1.5"
|
||||
androidTestApi "androidx.test.ext:junit:$androidxTestExtJunitVersion"
|
||||
androidTestApi "androidx.test.ext:truth:1.5.0"
|
||||
androidTestApi "androidx.test:core-ktx:1.5.0"
|
||||
androidTestApi "androidx.test:core:$androidx_test_version"
|
||||
androidTestApi "androidx.test:core-ktx:$testKtxVersion"
|
||||
androidTestApi "androidx.test:core:$androidxTestVersion"
|
||||
androidTestApi "androidx.test:runner:1.5.2"
|
||||
androidTestApi "org.jetbrains.kotlin:kotlin-test"
|
||||
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
testApi "androidx.arch.core:core-testing:$arch_version"
|
||||
testApi "junit:junit:$junit_version"
|
||||
androidTestApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
|
||||
testApi "androidx.arch.core:core-testing:$archVersion"
|
||||
testApi "junit:junit:$junitVersion"
|
||||
testApi "org.jetbrains.kotlin:kotlin-test"
|
||||
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinx_coroutines_version"
|
||||
testApi "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
|
||||
|
||||
// To use android test orchestrator
|
||||
androidTestUtil "androidx.test:orchestrator:1.4.2"
|
||||
|
@ -144,4 +147,10 @@ dependencies {
|
|||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
|
||||
exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jdk8"
|
||||
}
|
||||
|
||||
// conscrypt をUnitテストするための指定
|
||||
// https://github.com/google/conscrypt/issues/649
|
||||
|
||||
compileOnly "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
|
||||
testImplementation "org.conscrypt:conscrypt-openjdk-uber:$conscryptVersion"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package jp.juggler.crypt
|
||||
|
||||
import jp.juggler.util.data.encodeUTF8
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.Provider
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.experimental.xor
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Content-Encoding: aes128gcm のデコード
|
||||
* - 単体テストで中間状態を見たいので、状態を持つクラスとして実装した
|
||||
* - 鍵の導出が RFC8188生とWebPushの2通りある
|
||||
*/
|
||||
class Aes128GcmDecoder(
|
||||
// ペイロードのリーダー
|
||||
reader: ByteRangeReader,
|
||||
// 暗号プロバイダ
|
||||
private val provider: Provider = defaultSecurityProvider,
|
||||
) {
|
||||
companion object {
|
||||
private const val b0 = 0.toByte()
|
||||
private const val b1 = 1.toByte()
|
||||
private const val b2 = 2.toByte()
|
||||
|
||||
private val prefixWpInfoZ = "WebPush: info\u0000".encodeUTF8().toByteRange()
|
||||
private val ceAes128gcmZ = "Content-Encoding: aes128gcm\u0000".encodeUTF8().toByteRange()
|
||||
private val ceNonceZ = "Content-Encoding: nonce\u0000".encodeUTF8().toByteRange()
|
||||
}
|
||||
|
||||
// ペイロードのヘッダから導出される
|
||||
val salt: ByteRange
|
||||
val recordSize: Int
|
||||
val keyId: ByteRange
|
||||
val encryptedContent: ByteRange
|
||||
|
||||
// RFC8188のキー導出手順で計算される
|
||||
var prk = ByteRange.empty
|
||||
var key = ByteRange.empty
|
||||
var nonce = ByteRange.empty
|
||||
|
||||
init {
|
||||
// ペイロード内部のヘッダを読む
|
||||
salt = reader.readBytes(16)
|
||||
recordSize = reader.readUInt32()
|
||||
val keyIdLen = reader.readUInt8()
|
||||
keyId = reader.readBytes(keyIdLen)
|
||||
encryptedContent = reader.remainBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC8188のキー導出手順
|
||||
*/
|
||||
fun deriveKeyRfc8188(inputKey: ByteRange) {
|
||||
prk = hmacSha256(salt, inputKey).toByteRange()
|
||||
key = hmacSha256Plus1(prk, ceAes128gcmZ).toByteRange(end = 16)
|
||||
nonce = hmacSha256Plus1(prk, ceNonceZ).toByteRange(end = 12)
|
||||
}
|
||||
|
||||
/**
|
||||
* WebPush は inputKey の計算が少し増える
|
||||
*/
|
||||
fun deriveKeyWebPush(
|
||||
// receiver private key in X509 format
|
||||
receiverPrivateBytes: ByteArray,
|
||||
// receiver public key in 65bytes X9.62 uncompressed format
|
||||
receiverPublicBytes: ByteArray,
|
||||
// auth secrets created at subscription
|
||||
authSecret: ByteArray,
|
||||
) {
|
||||
// sender public key in 65bytes X9.62 uncompressed format
|
||||
val senderPublicBytes = keyId
|
||||
val sharedKeyBytes = provider.sharedKeyBytes(
|
||||
receiverPrivateBytes = receiverPrivateBytes,
|
||||
senderPublicBytes = senderPublicBytes.toByteArray(),
|
||||
)
|
||||
|
||||
// create input key material
|
||||
val ikm = hmacSha256Plus1(
|
||||
|
||||
hmacSha256(
|
||||
authSecret.toByteRange(),
|
||||
sharedKeyBytes,
|
||||
).toByteRange(),
|
||||
|
||||
ByteArrayOutputStream(130 + prefixWpInfoZ.size).apply {
|
||||
write(prefixWpInfoZ)
|
||||
write(receiverPublicBytes)
|
||||
write(senderPublicBytes)
|
||||
}.toByteRange()
|
||||
|
||||
).toByteRange(end = 32)
|
||||
|
||||
deriveKeyRfc8188(ikm)
|
||||
}
|
||||
|
||||
/**
|
||||
* レコードを順にデコードする
|
||||
*/
|
||||
fun decode(): ByteRange {
|
||||
if (recordSize < 1) error("invalid record size")
|
||||
|
||||
val iv = ByteArray(12)
|
||||
|
||||
nonce.copyElements(iv, length = 12)
|
||||
|
||||
var counter = 0L
|
||||
fun updateIv() {
|
||||
// 短いプッシュメッセージならカウンタは64bitを超えない。
|
||||
// カウンターをBEで12バイト表現すると上位8バイトは常に0なのでXOR演算する必要がない
|
||||
// 下位バイトだけXOR演算する
|
||||
var v = counter++
|
||||
if (v != 0L) {
|
||||
for (i in 0 until 8) {
|
||||
val pos = 11 - i
|
||||
iv[pos] = nonce[pos].xor(v.toByte())
|
||||
v = v.ushr(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding", provider)
|
||||
|
||||
val src = encryptedContent
|
||||
val bao = ByteArrayOutputStream(src.size)
|
||||
val deltaBuffer = ByteArray(recordSize)
|
||||
val end = src.size
|
||||
for (i in src.indices step recordSize) {
|
||||
val step = min(recordSize, end - i)
|
||||
// カウンター番号でivを更新
|
||||
updateIv()
|
||||
cipher.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
SecretKeySpec(key.ba, 0, 16, "AES-GCM"),
|
||||
GCMParameterSpec(GCM_TAG_BITS, iv),
|
||||
)
|
||||
val delta = cipher.doFinal(src.ba, src.start + i, step, deltaBuffer)
|
||||
var deltaLast = delta - 1
|
||||
// 末尾の0を削ると、その手前にデリミタが見えるはず
|
||||
while (deltaLast >= 0 && deltaBuffer[deltaLast] == b0) --deltaLast
|
||||
// 最後のレコードかどうかでデリミタが異なる
|
||||
val delimiter = if (i + step >= end) b2 else b1
|
||||
// デリミタがないのはエラー
|
||||
if (deltaLast < 0 || deltaBuffer[deltaLast] != delimiter) {
|
||||
error("missing delimiter")
|
||||
}
|
||||
// デリミタの手前までが有効なデータ
|
||||
bao.write(deltaBuffer, 0, deltaLast)
|
||||
}
|
||||
return bao.toByteRange()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package jp.juggler.crypt
|
||||
|
||||
import jp.juggler.util.data.encodeUTF8
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.Provider
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* Content-Encoding: aesgcm のデコード。
|
||||
* 中間状態のテストがしたいので状態をもつクラスとして実装した
|
||||
*/
|
||||
class AesGcmDecoder(
|
||||
// receiver private key in X509 format
|
||||
receiverPrivateBytes: ByteArray,
|
||||
// receiver public key in 65bytes X9.62 uncompressed format, created at subscription
|
||||
private val receiverPublicBytes: ByteArray,
|
||||
// auth secret, created at subscription
|
||||
authSecret: ByteArray,
|
||||
// sender public key in 65bytes X9.62 uncompressed format
|
||||
private val senderPublicBytes: ByteArray,
|
||||
// salt in HTTP header
|
||||
saltBytes: ByteArray,
|
||||
// provider for security functions
|
||||
private val provider: Provider = defaultSecurityProvider,
|
||||
) {
|
||||
companion object {
|
||||
private const val b0 = 0.toByte()
|
||||
|
||||
private val infoCeAuthZ = "Content-Encoding: auth\u0000".encodeUTF8().toByteRange()
|
||||
private val prefixCeAesGcmZP256Z = "Content-Encoding: aesgcm\u0000P-256\u0000".encodeUTF8()
|
||||
private val prefixCeNonceZP256Z = "Content-Encoding: nonce\u0000P-256\u0000".encodeUTF8()
|
||||
|
||||
fun ByteArrayOutputStream.writeLengthAndBytes(b: ByteArray) {
|
||||
val len = b.size
|
||||
write(len.shr(8).and(255))
|
||||
write(len.and(255))
|
||||
write(b)
|
||||
}
|
||||
}
|
||||
|
||||
private val salt = saltBytes.toByteRange()
|
||||
|
||||
val auth = authSecret.toByteRange()
|
||||
|
||||
val sharedKeyBytes = provider.sharedKeyBytes(
|
||||
receiverPrivateBytes = receiverPrivateBytes,
|
||||
senderPublicBytes = senderPublicBytes,
|
||||
)
|
||||
|
||||
//
|
||||
var ikm = ByteRange.empty
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var prk = ByteRange.empty
|
||||
var key = ByteRange.empty
|
||||
var nonce = ByteRange.empty
|
||||
|
||||
// The start index for each element within the buffer is:
|
||||
// value | length | start |
|
||||
// -----------------------------------------
|
||||
// 'Content-Encoding: '| 18 | 0 |
|
||||
// type | len | 18 |
|
||||
// nul byte | 1 | 18 + len |
|
||||
// 'P-256' | 5 | 19 + len |
|
||||
// nul byte | 1 | 24 + len |
|
||||
// client key length | 2 | 25 + len |
|
||||
// client key | 65 | 27 + len |
|
||||
// server key length | 2 | 92 + len |
|
||||
// server key | 65 | 94 + len |
|
||||
private fun createInfo(
|
||||
prefix: ByteArray,
|
||||
clientPublicKey: ByteArray, // 65 byte
|
||||
serverPublicKey: ByteArray, // 65 byte
|
||||
) = ByteArrayOutputStream(120).apply {
|
||||
write(prefix)
|
||||
// For the purposes of push encryption the length of the keys will always be 65 bytes.
|
||||
writeLengthAndBytes(clientPublicKey)
|
||||
writeLengthAndBytes(serverPublicKey)
|
||||
}.toByteRange()
|
||||
|
||||
fun deriveKey() {
|
||||
// input key material の導出はWebPushの仕様に依存する
|
||||
ikm = hmacSha256Plus1(
|
||||
hmacSha256(auth, sharedKeyBytes).toByteRange(),
|
||||
infoCeAuthZ
|
||||
).toByteRange(end = 32)
|
||||
|
||||
// ikm, salt から prk, key, nonce を導出する
|
||||
|
||||
prk = hmacSha256(salt, ikm).toByteRange()
|
||||
|
||||
key = hmacSha256Plus1(
|
||||
prk,
|
||||
createInfo(
|
||||
prefix = prefixCeAesGcmZP256Z,
|
||||
clientPublicKey = receiverPublicBytes,
|
||||
serverPublicKey = senderPublicBytes,
|
||||
)
|
||||
).toByteRange(end = 16)
|
||||
|
||||
nonce = hmacSha256Plus1(
|
||||
prk,
|
||||
createInfo(
|
||||
prefix = prefixCeNonceZP256Z,
|
||||
clientPublicKey = receiverPublicBytes,
|
||||
serverPublicKey = senderPublicBytes,
|
||||
)
|
||||
).toByteRange(end = 12)
|
||||
}
|
||||
|
||||
fun decode(src: ByteRange): ByteRange {
|
||||
|
||||
val cip = Cipher.getInstance("AES/GCM/NoPadding", provider)
|
||||
cip.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
SecretKeySpec(key.ba, key.start, key.size, "AES"),
|
||||
GCMParameterSpec(
|
||||
GCM_TAG_BITS,
|
||||
nonce.ba, nonce.start, nonce.size
|
||||
),
|
||||
)
|
||||
var dst = cip.doFinal(src.ba, src.start, src.size).toByteRange()
|
||||
|
||||
// 多分不要だと思うが、
|
||||
// https://greenbytes.de/tech/webdav/draft-ietf-httpbis-encryption-encoding-02.html
|
||||
// にあるレコード先頭のパディングを除去する
|
||||
// テキストメッセージなら nul文字が含まれないのでヒットしないはず
|
||||
if (dst.size >= 3 && dst[2] == b0) {
|
||||
val reader = dst.byteRangeReader()
|
||||
val padLen = 2 + reader.readUInt16()
|
||||
reader.skip(padLen)
|
||||
dst = reader.remainBytes()
|
||||
}
|
||||
|
||||
// 先頭の空白を除去する
|
||||
var i = 0
|
||||
while (i < dst.size && dst[i] in 0..32) ++i
|
||||
if (i > 0) dst = dst.subRange(start = i)
|
||||
|
||||
return dst
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package jp.juggler.crypt
|
||||
|
||||
import org.apache.commons.codec.binary.Base64.encodeBase64String
|
||||
import org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/**
|
||||
* バイト配列の一部を参照する
|
||||
*/
|
||||
class ByteRange(
|
||||
val ba: ByteArray,
|
||||
val start: Int,
|
||||
end: Int,
|
||||
) {
|
||||
companion object {
|
||||
val empty = ByteRange(ByteArray(0), 0, 0)
|
||||
val UTF8 = StandardCharsets.UTF_8.name()!!
|
||||
}
|
||||
|
||||
val size = end - start
|
||||
val indices = 0 until size
|
||||
|
||||
fun subRange(start: Int = 0, end: Int = size) =
|
||||
ByteRange(ba, start = this.start + start, end = this.start + end)
|
||||
|
||||
operator fun get(pos: Int) = ba[start + pos]
|
||||
|
||||
fun toByteArray() =
|
||||
ba.copyOfRange(start, start + size)
|
||||
|
||||
fun elementAtOrNull(pos: Int) =
|
||||
if (pos in indices) ba.elementAtOrNull(start + pos) else null
|
||||
|
||||
fun encodeBase64Url(): String =
|
||||
encodeBase64URLSafeString(toByteArray())
|
||||
|
||||
fun encodeBase64(): String =
|
||||
encodeBase64String(toByteArray())
|
||||
|
||||
fun decodeUTF8() =
|
||||
java.lang.String(ba, start, size, UTF8)
|
||||
|
||||
fun copyElements(
|
||||
dst: ByteArray,
|
||||
dstOffset: Int = 0,
|
||||
srcOffset: Int = 0,
|
||||
length: Int = size - srcOffset,
|
||||
) = System.arraycopy(ba, start + srcOffset, dst, dstOffset, length)
|
||||
}
|
||||
|
||||
fun ByteArray.toByteRange(start: Int = 0, end: Int = size) = ByteRange(this, start, end)
|
||||
|
||||
fun ByteArrayOutputStream.write(src: ByteRange, start: Int = 0, len: Int = src.size) =
|
||||
write(src.ba, src.start + start, len)
|
||||
|
||||
fun ByteArrayOutputStream.toByteRange(start: Int = 0, end: Int = size()) =
|
||||
toByteArray().toByteRange(start = start, end = end)
|
|
@ -0,0 +1,53 @@
|
|||
package jp.juggler.crypt
|
||||
|
||||
class ByteRangeReader(
|
||||
private val src: ByteRange,
|
||||
) {
|
||||
private val end = src.size
|
||||
private var pos = 0
|
||||
|
||||
fun skip(length: Int) {
|
||||
pos += length
|
||||
}
|
||||
|
||||
fun readBytes(size: Int = end - pos): ByteRange {
|
||||
if (pos + size > end) error("unexpected end.")
|
||||
val rv = src.subRange(pos, pos + size)
|
||||
pos += size
|
||||
return rv
|
||||
}
|
||||
|
||||
/**
|
||||
* 残り全部
|
||||
*/
|
||||
fun remainBytes() = readBytes()
|
||||
|
||||
fun readUInt8(): Int {
|
||||
if (pos >= end) error("unexpected end.")
|
||||
val b = src[pos++]
|
||||
return b.toInt().and(255)
|
||||
}
|
||||
|
||||
fun readUInt16(): Int {
|
||||
if (pos + 2 > end) error("unexpected end.")
|
||||
// Big Endian
|
||||
val b0 = src[pos].toInt().and(255).shl(8)
|
||||
val b1 = src[pos + 1].toInt().and(255)
|
||||
pos += 2
|
||||
return b0.or(b1)
|
||||
}
|
||||
|
||||
fun readUInt32(): Int {
|
||||
if (pos + 4 > end) error("unexpected end.")
|
||||
// Big Endian
|
||||
val b0 = src[pos].toInt().and(255).shl(24)
|
||||
val b1 = src[pos + 1].toInt().and(255).shl(16)
|
||||
val b2 = src[pos + 2].toInt().and(255).shl(8)
|
||||
val b3 = src[pos + 3].toInt().and(255)
|
||||
pos += 4
|
||||
return b0.or(b1).or(b2).or(b3)
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteRange.byteRangeReader() = ByteRangeReader(this)
|
||||
fun ByteArray.byteRangeReader() = ByteRangeReader(this.toByteRange())
|
|
@ -0,0 +1,195 @@
|
|||
package jp.juggler.crypt
|
||||
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.bouncycastle.jce.ECPointUtil
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec
|
||||
import org.conscrypt.Conscrypt
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.interfaces.ECPrivateKey
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.ECPrivateKeySpec
|
||||
import java.security.spec.ECPublicKeySpec
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
val defaultSecurityProvider by lazy {
|
||||
Conscrypt.newProvider()!!.also { Security.addProvider(it) }
|
||||
}
|
||||
|
||||
const val DEFAULT_CURVE_NAME = "secp256r1"
|
||||
|
||||
const val GCM_TAG_BITS = 16 * 8
|
||||
|
||||
// HKDFのExpandの末尾に付与する
|
||||
private val hkdfTrailBytes = ByteArray(1) { 1 }.toByteRange()
|
||||
|
||||
// k=v;k=v;... を解釈する
|
||||
fun String.parseSemicolon() = split(";").map { pair ->
|
||||
pair.split("=", limit = 2).map { it.trim() }
|
||||
}.mapNotNull {
|
||||
when {
|
||||
it.isEmpty() -> null
|
||||
else -> it[0] to it.elementAtOrNull(1)
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
// ECPrivateKey のS値を32バイトの配列にコピーする
|
||||
// BigInteger.toByteArrayは可変長なので、長さの調節を行う
|
||||
fun encodePrivateKeyRaw(key: ECPrivateKey): ByteArray {
|
||||
val srcBytes = key.s.toByteArray()
|
||||
return when {
|
||||
srcBytes.size == 32 -> srcBytes
|
||||
// 32バイト以内なら先頭にゼロを詰めて返す
|
||||
srcBytes.size < 32 -> ByteArray(32).also {
|
||||
System.arraycopy(
|
||||
srcBytes, 0,
|
||||
it, it.size - srcBytes.size,
|
||||
srcBytes.size
|
||||
)
|
||||
}
|
||||
// ビッグエンディアンの先頭に符号ビットが付与されるので、32バイトに収まらない場合がある
|
||||
// 末尾32バイト分を返す
|
||||
else -> ByteArray(32).also {
|
||||
System.arraycopy(
|
||||
srcBytes, srcBytes.size - it.size,
|
||||
it, 0,
|
||||
it.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebPushのp256dh、つまり公開鍵を X9.62 uncompressed format で符号化したバイト列を作る。
|
||||
* - 出力の長さは65バイト
|
||||
*/
|
||||
fun encodeP256Dh(src: ECPublicKey): ByteArray = src.run {
|
||||
val bitsInByte = 8
|
||||
val keySizeBytes = (params.order.bitLength() + bitsInByte - 1) / bitsInByte
|
||||
return ByteArray(1 + 2 * keySizeBytes).also { dst ->
|
||||
var offset = 0
|
||||
dst[offset++] = 0x04
|
||||
w.affineX.toByteArray().let { x ->
|
||||
when {
|
||||
x.size <= keySizeBytes ->
|
||||
System.arraycopy(
|
||||
x, 0,
|
||||
dst, offset + keySizeBytes - x.size,
|
||||
x.size
|
||||
)
|
||||
|
||||
x.size == keySizeBytes + 1 && x[0].toInt() == 0 ->
|
||||
System.arraycopy(
|
||||
x, 1,
|
||||
dst, offset,
|
||||
keySizeBytes
|
||||
)
|
||||
|
||||
else -> error("x value is too large")
|
||||
}
|
||||
}
|
||||
offset += keySizeBytes
|
||||
w.affineY.toByteArray().let { y ->
|
||||
when {
|
||||
y.size <= keySizeBytes -> System.arraycopy(
|
||||
y, 0,
|
||||
dst, offset + keySizeBytes - y.size,
|
||||
y.size
|
||||
)
|
||||
|
||||
y.size == keySizeBytes + 1 && y[0].toInt() == 0 -> System.arraycopy(
|
||||
y, 1,
|
||||
dst, offset,
|
||||
keySizeBytes
|
||||
)
|
||||
else -> error("y value is too large")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JavaScriptのcreateECDHがエンコードした秘密鍵をデコードする
|
||||
// https://github.com/nodejs/node/blob/main/lib/internal/crypto/diffiehellman.js#L232
|
||||
// https://github.com/nodejs/node/blob/main/src/crypto/crypto_ec.cc#L265
|
||||
fun Provider.decodePrivateKeyRaw(srcBytes: ByteArray): ECPrivateKey {
|
||||
// 符号拡張が起きないように先頭に0を追加する
|
||||
val newBytes = ByteArray(srcBytes.size + 1).also {
|
||||
System.arraycopy(
|
||||
srcBytes, 0,
|
||||
it, it.size - srcBytes.size,
|
||||
srcBytes.size
|
||||
)
|
||||
}
|
||||
|
||||
val s = BigInteger(newBytes)
|
||||
|
||||
// テキトーに鍵を作る
|
||||
val keyPair = generateKeyPair()
|
||||
// params部分を取り出す
|
||||
val ecParameterSpec = (keyPair.private as ECPrivateKey).params
|
||||
// s値を指定して鍵を作る
|
||||
val privateKeySpec = ECPrivateKeySpec(s, ecParameterSpec)
|
||||
return KeyFactory.getInstance("EC", this)
|
||||
.generatePrivate(privateKeySpec) as ECPrivateKey
|
||||
}
|
||||
|
||||
/**
|
||||
* ECの鍵ペアを作成する
|
||||
*/
|
||||
fun Provider.generateKeyPair(curveName: String = DEFAULT_CURVE_NAME): KeyPair =
|
||||
KeyPairGenerator.getInstance("EC", this).apply {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
initialize(ECGenParameterSpec(curveName))
|
||||
}.genKeyPair() ?: error("genKeyPair returns null")
|
||||
|
||||
fun Mac.update(br: ByteRange) = update(br.ba, br.start, br.size)
|
||||
|
||||
fun hmacSha256(salt: ByteRange, keyMaterial: ByteRange, plus1: Boolean = false) =
|
||||
Mac.getInstance("HMacSHA256").run {
|
||||
init(SecretKeySpec(salt.ba, salt.start, salt.size, "HMacSHA256"))
|
||||
update(keyMaterial)
|
||||
if (plus1) update(hkdfTrailBytes)
|
||||
doFinal()
|
||||
} ?: error("Mac.doFinal returns null")
|
||||
|
||||
/**
|
||||
* HKDF の2段階目は末尾に \01 を追加する
|
||||
*/
|
||||
fun hmacSha256Plus1(salt: ByteRange, keyMaterial: ByteRange) =
|
||||
hmacSha256(salt, keyMaterial, plus1 = true)
|
||||
|
||||
// 鍵全体をX509でエンコードしたものをデコードする
|
||||
fun Provider.decodePrivateKeyX509(src: ByteArray) =
|
||||
KeyFactory.getInstance("EC", this)
|
||||
.generatePrivate(PKCS8EncodedKeySpec(src))
|
||||
?: error("generatePrivate returns null")
|
||||
|
||||
/**
|
||||
* p256dh(65バイト)から公開鍵を復元する
|
||||
* - ECPointUtil.decodePoint はバイト配列全体を指定するしかないので、src引数はByteArrayである
|
||||
*/
|
||||
fun Provider.decodeP256dh(src: ByteArray, curveName: String = DEFAULT_CURVE_NAME): ECPublicKey {
|
||||
val spec = ECNamedCurveTable.getParameterSpec(curveName)
|
||||
val params = ECNamedCurveSpec(curveName, spec.curve, spec.g, spec.n)
|
||||
val pubKeySpec = ECPublicKeySpec(
|
||||
ECPointUtil.decodePoint(params.curve, src),
|
||||
params
|
||||
)
|
||||
return KeyFactory.getInstance("EC", this)
|
||||
.generatePublic(pubKeySpec) as ECPublicKey
|
||||
}
|
||||
|
||||
fun Provider.sharedKeyBytes(
|
||||
receiverPrivateBytes: ByteArray,
|
||||
senderPublicBytes: ByteArray,
|
||||
) = KeyAgreement.getInstance("ECDH", this).run {
|
||||
val receiverPrivate = decodePrivateKeyX509(receiverPrivateBytes)
|
||||
val senderPublic = decodeP256dh(senderPublicBytes)
|
||||
init(receiverPrivate)
|
||||
doPhase(senderPublic, true)
|
||||
generateSecret()
|
||||
}.toByteRange()
|
|
@ -0,0 +1,58 @@
|
|||
package jp.juggler.util.data
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* Base128 エンコーディング
|
||||
*
|
||||
* FCMで何文字送れるか試験したところ、文字種により送れる文字数に差異はあった。
|
||||
* n=4050 c=a 1bytes
|
||||
* n=4050 c=\u0000 1bytes
|
||||
* n=4050 c=\u000a 1bytes
|
||||
* n=2025 c=\u00a9 2bytes
|
||||
* n=1350 c=\u82b1 3bytes
|
||||
*
|
||||
* - jsonのエスケープなどは考慮しなくてよさそう。
|
||||
* - UTF-8表現でバイト数が増えると送れる文字数が減る。
|
||||
*
|
||||
* UTF-8の長い表現を使うと使えるビットがむしろ減るので、
|
||||
* UTF-8の 0x00-0x7f の範囲の文字にエンコードするのが効率が良さそうだった。
|
||||
*
|
||||
*/
|
||||
object Base128 {
|
||||
|
||||
fun String.decodeBase128(): ByteArray =
|
||||
ByteArrayOutputStream(this.length).also {
|
||||
var bits = 0
|
||||
var bitsUsed = 0
|
||||
for (c in this) {
|
||||
bits = bits.shl(7).or(c.code.and(0x7f))
|
||||
bitsUsed += 7
|
||||
if (bitsUsed >= 8) {
|
||||
val outByte = bits.shr(bitsUsed - 8).and(0xff)
|
||||
it.write(outByte)
|
||||
bitsUsed -= 8
|
||||
}
|
||||
}
|
||||
// bitsUsedに8未満のbitが残ることがあるが、末尾のパディングなので読み捨てる
|
||||
}.toByteArray()
|
||||
|
||||
fun ByteArray.encodeBase128(): String =
|
||||
StringBuilder(this.size).also {
|
||||
var bits = 0
|
||||
var bitsUsed = 0
|
||||
for (inByte in this) {
|
||||
bits = bits.shl(8).or(inByte.toInt().and(255))
|
||||
bitsUsed += 8
|
||||
while (bitsUsed >= 7) {
|
||||
val outBits = bits.shr(bitsUsed - 7).and(0x7f)
|
||||
bitsUsed -= 7
|
||||
it.append(outBits.toChar())
|
||||
}
|
||||
}
|
||||
if (bitsUsed > 0) {
|
||||
val outBits = bits.shl(7 - bitsUsed).and(0x7f)
|
||||
it.append(outBits.toChar())
|
||||
}
|
||||
}.toString()
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
package jp.juggler.util.data
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/**
|
||||
* BinPack format
|
||||
*
|
||||
* アプリサーバからプッシュサービス経由で受け取るデータを包む。
|
||||
* - 構造的にはJSONに似ている。階層を持つデータをエンコードできる。
|
||||
*
|
||||
* Pros:
|
||||
* - バイト配列をエンコードできる。
|
||||
* - 整数、浮動少数をエンコードできる。
|
||||
* - エンコードされたデータは外部ライブラリの仕様変更に影響されない。
|
||||
* - 内部実装のenumの名前やorderに依存しない。
|
||||
*
|
||||
* Cons:
|
||||
* - ORMをサポートしない。
|
||||
* - デコード結果の解読は生jsonと同程度に面倒。
|
||||
* - ByteArray以外のプリミティブ配列をサポートしない。
|
||||
*
|
||||
* Usage:
|
||||
* ListやMap をエンコード/デコードしたら BinPackMap や BinPackList になる。
|
||||
* 利便性のために string(k), bytes(k), map(k) などのメソッドがある。
|
||||
*
|
||||
* Background:
|
||||
* WebPushで暗号化されたバイト配列とHTTPヘッダ情報と宛先ハッシュを送るため
|
||||
* バイナリの汎用データフォーマットが欲しかった。
|
||||
* 既存のものをいくつか検討した結果、300行程度で済むなら自前実装
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
fun Any?.encodeBinPack(): ByteArray =
|
||||
ByteArrayOutputStream().use {
|
||||
BinPackWriter(it).writeValue(this)
|
||||
it.flush()
|
||||
it.toByteArray()
|
||||
}
|
||||
|
||||
fun ByteArray.decodeBinPack(): Any? =
|
||||
ByteArrayInputStream(this).use {
|
||||
BinPackReader(it).readValue()
|
||||
}
|
||||
|
||||
fun ByteArray.decodeBinPackMap() = decodeBinPack() as? BinPackMap
|
||||
|
||||
@Suppress("unused")
|
||||
fun ByteArray.decodeBinPackList() = decodeBinPack() as? BinPackList
|
||||
|
||||
class BinPackMap() : HashMap<Any?, Any?>() {
|
||||
constructor(vararg pairs: Pair<Any?, Any?>) : this() {
|
||||
putAll(pairs)
|
||||
}
|
||||
|
||||
fun string(k: Any?) = get(k) as? String
|
||||
fun bytes(k: Any?) = get(k) as? ByteArray
|
||||
fun map(k: Any?) = get(k) as? BinPackMap
|
||||
}
|
||||
|
||||
class BinPackList() : ArrayList<Any?>() {
|
||||
constructor(vararg args: Any?) : this() {
|
||||
addAll(args)
|
||||
}
|
||||
|
||||
fun string(k: Int) = elementAtOrNull(k) as? String
|
||||
@Suppress("unused")
|
||||
fun bytes(k: Int) = elementAtOrNull(k) as? ByteArray
|
||||
fun map(k: Int) = elementAtOrNull(k) as? BinPackMap
|
||||
}
|
||||
|
||||
private enum class ValueType(val id: Int) {
|
||||
VNull(0),
|
||||
VTrue(1),
|
||||
VFalse(2),
|
||||
VBytes(3),
|
||||
VString(4),
|
||||
VList(5),
|
||||
VMap(6),
|
||||
VDouble(7),
|
||||
VFloat(8),
|
||||
|
||||
@Suppress("unused")
|
||||
VInt8(10), // Byte, signed
|
||||
VInt16(11),
|
||||
VInt32(12),
|
||||
VInt64(13),
|
||||
VUInt16(15), // Char, unsigned
|
||||
;
|
||||
|
||||
companion object {
|
||||
val valuesCache = values()
|
||||
val idMap = valuesCache.associateBy { it.id }
|
||||
}
|
||||
}
|
||||
|
||||
private class BinPackWriter(
|
||||
private val out: OutputStream,
|
||||
) {
|
||||
private fun writeInt16(n: Int) {
|
||||
out.write(n)
|
||||
out.write(n.ushr(8))
|
||||
}
|
||||
|
||||
private fun writeInt32(n: Int) {
|
||||
out.write(n)
|
||||
out.write(n.ushr(8))
|
||||
out.write(n.ushr(16))
|
||||
out.write(n.ushr(24))
|
||||
}
|
||||
|
||||
private fun writeInt64(n: Long) {
|
||||
writeInt32(n.toInt())
|
||||
writeInt32(n.ushr(32).toInt())
|
||||
}
|
||||
|
||||
private fun writeDouble(n: Double) = writeInt64(n.toRawBits())
|
||||
private fun writeFloat(n: Float) = writeInt32(n.toRawBits())
|
||||
|
||||
private fun writeVType(t: ValueType) = out.write(t.id)
|
||||
private fun writeSize(n: Int) = writeInt32(n)
|
||||
|
||||
fun writeValue(value: Any?) {
|
||||
when (value) {
|
||||
null -> writeVType(ValueType.VNull)
|
||||
true -> writeVType(ValueType.VTrue)
|
||||
false -> writeVType(ValueType.VFalse)
|
||||
is ByteArray -> {
|
||||
writeVType(ValueType.VBytes)
|
||||
writeSize(value.size)
|
||||
out.write(value)
|
||||
}
|
||||
|
||||
is String -> {
|
||||
writeVType(ValueType.VString)
|
||||
val encoded = value.toByteArray(StandardCharsets.UTF_8)
|
||||
writeSize(encoded.size)
|
||||
out.write(encoded)
|
||||
}
|
||||
|
||||
is Collection<*> -> {
|
||||
writeVType(ValueType.VList)
|
||||
writeSize(value.size)
|
||||
for (x in value) {
|
||||
writeValue(x)
|
||||
}
|
||||
}
|
||||
|
||||
is Array<*> -> {
|
||||
writeVType(ValueType.VList)
|
||||
writeSize(value.size)
|
||||
for (x in value) {
|
||||
writeValue(x)
|
||||
}
|
||||
}
|
||||
|
||||
is Map<*, *> -> {
|
||||
writeVType(ValueType.VMap)
|
||||
val entries = value.entries
|
||||
writeSize(entries.size)
|
||||
for (entry in value.entries) {
|
||||
writeValue(entry.key)
|
||||
writeValue(entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
is Double -> {
|
||||
writeVType(ValueType.VDouble)
|
||||
writeDouble(value)
|
||||
}
|
||||
|
||||
is Float -> {
|
||||
writeVType(ValueType.VFloat)
|
||||
writeFloat(value)
|
||||
}
|
||||
|
||||
is Long -> {
|
||||
writeVType(ValueType.VInt64)
|
||||
writeInt64(value)
|
||||
}
|
||||
|
||||
is Int -> {
|
||||
writeVType(ValueType.VInt32)
|
||||
writeInt32(value)
|
||||
}
|
||||
|
||||
is Short -> {
|
||||
writeVType(ValueType.VInt16)
|
||||
writeInt16(value.toInt())
|
||||
}
|
||||
|
||||
is Char -> {
|
||||
writeVType(ValueType.VUInt16)
|
||||
writeInt16(value.code)
|
||||
}
|
||||
|
||||
is Byte -> {
|
||||
writeVType(ValueType.VInt8)
|
||||
out.write(value.toInt())
|
||||
}
|
||||
|
||||
else -> error("unsupported type ${value.javaClass.simpleName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BinPackReader(
|
||||
private val ins: InputStream,
|
||||
) {
|
||||
private fun readUInt8(): Int {
|
||||
val n = ins.read().and(255)
|
||||
if (n == -1) error("unexpected end")
|
||||
return n
|
||||
}
|
||||
|
||||
private fun readUInt16(): Int {
|
||||
val b0 = readUInt8()
|
||||
val b1 = readUInt8().shl(8)
|
||||
return b0.or(b1)
|
||||
}
|
||||
|
||||
private fun readInt32(): Int {
|
||||
val b0 = readUInt8()
|
||||
val b1 = readUInt8().shl(8)
|
||||
val b2 = readUInt8().shl(16)
|
||||
val b3 = readUInt8().shl(24)
|
||||
return b0.or(b1).or(b2).or(b3)
|
||||
}
|
||||
|
||||
private fun readInt64(): Long {
|
||||
val low = readInt32().toLong().and(0xFFFFFFFFL)
|
||||
val high = readInt32().toLong().shl(32)
|
||||
return low.or(high)
|
||||
}
|
||||
|
||||
private fun readFloat() = Float.fromBits(readInt32())
|
||||
|
||||
private fun readDouble() = Double.fromBits(readInt64())
|
||||
|
||||
private fun readBytes(size: Int) = ByteArray(size).also { dst ->
|
||||
var nRead = 0
|
||||
while (nRead < size) {
|
||||
val delta = ins.read(dst, nRead, size - nRead)
|
||||
if (delta < 0) error("unexpected end.")
|
||||
if (delta > 0) nRead += delta
|
||||
}
|
||||
}
|
||||
|
||||
private fun readSize() = readInt32()
|
||||
|
||||
fun readValue(): Any? {
|
||||
val id = readUInt8()
|
||||
return when (ValueType.idMap[id]) {
|
||||
null -> error("unknown type id=$id")
|
||||
ValueType.VNull -> null
|
||||
ValueType.VTrue -> true
|
||||
ValueType.VFalse -> false
|
||||
ValueType.VBytes -> readBytes(readSize())
|
||||
ValueType.VString -> readBytes(readSize()).toString(StandardCharsets.UTF_8)
|
||||
ValueType.VList -> BinPackList().apply {
|
||||
val size = readSize()
|
||||
ensureCapacity(size)
|
||||
repeat(size) { add(readValue()) }
|
||||
}
|
||||
ValueType.VMap -> BinPackMap().apply {
|
||||
repeat(readSize()) {
|
||||
val k = readValue()
|
||||
val v = readValue()
|
||||
put(k, v)
|
||||
}
|
||||
}
|
||||
ValueType.VDouble -> readDouble()
|
||||
ValueType.VFloat -> readFloat()
|
||||
ValueType.VInt8 -> readUInt8().toByte()
|
||||
ValueType.VInt16 -> readUInt16().toShort()
|
||||
ValueType.VInt32 -> readInt32()
|
||||
ValueType.VInt64 -> readInt64()
|
||||
ValueType.VUInt16 -> readUInt16().toChar()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,46 +5,48 @@ import android.net.Uri
|
|||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.util.Base64
|
||||
import androidx.core.net.toUri
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import org.apache.commons.codec.binary.Base64.*
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object StringUtils {
|
||||
private val log = LogCategory("StringUtils")
|
||||
|
||||
val log = LogCategory("StringUtils")
|
||||
private const val HEX_LOWER = "0123456789abcdef"
|
||||
|
||||
val hexLower =
|
||||
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
// BDI制御文字からその制御文字を閉じる文字を得るためのマップ
|
||||
val SanitizeBdiMap = HashMap<Char, Char>().apply {
|
||||
|
||||
// BDI制御文字からその制御文字を閉じる文字を得るためのマップ
|
||||
val sanitizeBdiMap = HashMap<Char, Char>().apply {
|
||||
val PDF = 0x202C.toChar() // Pop directional formatting (PDF)
|
||||
this[0x202A.toChar()] = PDF // Left-to-right embedding (LRE)
|
||||
this[0x202B.toChar()] = PDF // Right-to-left embedding (RLE)
|
||||
this[0x202D.toChar()] = PDF // Left-to-right override (LRO)
|
||||
this[0x202E.toChar()] = PDF // Right-to-left override (RLO)
|
||||
|
||||
val PDF = 0x202C.toChar() // Pop directional formatting (PDF)
|
||||
this[0x202A.toChar()] = PDF // Left-to-right embedding (LRE)
|
||||
this[0x202B.toChar()] = PDF // Right-to-left embedding (RLE)
|
||||
this[0x202D.toChar()] = PDF // Left-to-right override (LRO)
|
||||
this[0x202E.toChar()] = PDF // Right-to-left override (RLO)
|
||||
val PDI = 0x2069.toChar() // Pop directional isolate (PDI)
|
||||
this[0x2066.toChar()] = PDI // Left-to-right isolate (LRI)
|
||||
this[0x2067.toChar()] = PDI // Right-to-left isolate (RLI)
|
||||
this[0x2068.toChar()] = PDI // First strong isolate (FSI)
|
||||
|
||||
val PDI = 0x2069.toChar() // Pop directional isolate (PDI)
|
||||
this[0x2066.toChar()] = PDI // Left-to-right isolate (LRI)
|
||||
this[0x2067.toChar()] = PDI // Right-to-left isolate (RLI)
|
||||
this[0x2068.toChar()] = PDI // First strong isolate (FSI)
|
||||
|
||||
// private const val ALM = 0x061c.toChar() // Arabic letter mark (ALM)
|
||||
// private const val LRM = 0x200E.toChar() // Left-to-right mark (LRM)
|
||||
// private const val RLM = 0x200F.toChar() // Right-to-left mark (RLM)
|
||||
}
|
||||
// private const val ALM = 0x061c.toChar() // Arabic letter mark (ALM)
|
||||
// private const val LRM = 0x200E.toChar() // Left-to-right mark (LRM)
|
||||
// private const val RLM = 0x200F.toChar() // Right-to-left mark (RLM)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// ByteArray
|
||||
|
||||
fun ByteArray.encodeBase64Url(): String =
|
||||
Base64.encodeToString(this, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
encodeBase64URLSafeString(this)
|
||||
|
||||
fun ByteArray.encodeBase64(): String =
|
||||
encodeBase64String(this)
|
||||
|
||||
fun String.decodeBase64(): ByteArray =
|
||||
decodeBase64(this)
|
||||
|
||||
fun ByteArray.digestSHA256(): ByteArray {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
|
@ -171,8 +173,8 @@ fun ByteArray.encodeHex(): String {
|
|||
}
|
||||
|
||||
fun StringBuilder.appendHex2(value: Int): StringBuilder {
|
||||
this.append(StringUtils.hexLower[(value shr 4) and 15])
|
||||
this.append(StringUtils.hexLower[value and 15])
|
||||
append(HEX_LOWER[(value shr 4) and 15])
|
||||
append(HEX_LOWER[value and 15])
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -181,8 +183,8 @@ fun ByteArray.encodeHexLower(): String {
|
|||
val sb = StringBuilder(size * 2)
|
||||
for (i in 0 until size) {
|
||||
val value = this[i].toInt()
|
||||
sb.append(StringUtils.hexLower[(value shr 4) and 15])
|
||||
sb.append(StringUtils.hexLower[value and 15])
|
||||
sb.append(HEX_LOWER[(value shr 4) and 15])
|
||||
sb.append(HEX_LOWER[value and 15])
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
@ -220,7 +222,7 @@ fun String.sanitizeBDI(): String {
|
|||
// 文字列をスキャンしてBDI制御文字をスタックに入れていく
|
||||
var stack: LinkedList<Char>? = null
|
||||
for (c in this) {
|
||||
val closer = StringUtils.sanitizeBdiMap[c]
|
||||
val closer = SanitizeBdiMap[c]
|
||||
if (closer != null) {
|
||||
if (stack == null) stack = LinkedList()
|
||||
stack.add(closer)
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
package jp.juggler.util.log
|
||||
|
||||
import android.util.Log
|
||||
import jp.juggler.base.BuildConfig
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlin.math.min
|
||||
|
||||
object AdbLog {
|
||||
private const val APP_TAG = "AppTag_PushReceiver"
|
||||
private const val MAX_LOG_LENGTH = 4000
|
||||
|
||||
@Suppress("RegExpSimplifiable")
|
||||
val reAnonymousClass = """(\.\$[0-9]+)+$""".toRegex()
|
||||
|
||||
private val callStackTag: String
|
||||
get() {
|
||||
// DO NOT switch this to Thread.getCurrentThread().getStackTrace(). The test will pass
|
||||
// because Robolectric runs them on the JVM but on Android the elements are different.
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
val trace = Throwable().stackTrace
|
||||
val stackTraceElement = trace.elementAtOrNull(4)
|
||||
?: error("callStackTag: stacktrace didn't have enough elements: are you using proguard?")
|
||||
|
||||
stackTraceElement.fileName.notEmpty()
|
||||
?.let {
|
||||
when (val lastDotPos = it.lastIndexOf('.')) {
|
||||
-1 -> it
|
||||
else -> it.substring(0, lastDotPos)
|
||||
}
|
||||
}
|
||||
?.let { return it }
|
||||
|
||||
return reAnonymousClass.replace(stackTraceElement.className, "")
|
||||
.let { it.substring(it.lastIndexOf('.') + 1) }
|
||||
}
|
||||
|
||||
private fun Throwable.dump(): String {
|
||||
// Don't replace this with Log.getStackTraceString() - it hides
|
||||
// UnknownHostException, which is not what we want.
|
||||
val sw = StringWriter(256)
|
||||
val pw = PrintWriter(sw, false)
|
||||
this.printStackTrace(pw)
|
||||
pw.flush()
|
||||
return sw.toString()
|
||||
}
|
||||
|
||||
private fun printlnOrWtf(priority: Int, message: String) {
|
||||
try {
|
||||
if (priority == Log.ASSERT) {
|
||||
Log.wtf(APP_TAG, message)
|
||||
} else {
|
||||
Log.println(priority, APP_TAG, message)
|
||||
}
|
||||
} catch (ignored: Throwable) {
|
||||
// 単体テストで呼ばれた?
|
||||
println(message)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun splitLines(
|
||||
prefix: String,
|
||||
message: CharSequence,
|
||||
block: (CharSequence) -> Unit,
|
||||
) {
|
||||
val limit = MAX_LOG_LENGTH - prefix.length
|
||||
if (message.length < limit) {
|
||||
block(prefix + message)
|
||||
} else {
|
||||
// Split by line, then ensure each line can fit into Log's maximum length.
|
||||
val length = message.length
|
||||
var i = 0
|
||||
while (i < length) {
|
||||
val newline = message.indexOf('\n', i).takeIf { it >= 0 } ?: length
|
||||
do {
|
||||
val end = min(newline, i + limit)
|
||||
val part = message.subSequence(i, end)
|
||||
block(prefix + part)
|
||||
i = end
|
||||
} while (i < newline)
|
||||
// skip \n
|
||||
++i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun log(
|
||||
priority: Int,
|
||||
ex: Throwable? = null,
|
||||
messageArg: String? = null,
|
||||
prefixArg: String? = null,
|
||||
) {
|
||||
if (priority < Log.INFO && !BuildConfig.DEBUG) return
|
||||
|
||||
// 本文の先頭に付与するprefix
|
||||
val tag = (prefixArg ?: callStackTag)
|
||||
val prefix = when {
|
||||
tag.isBlank() -> ""
|
||||
else -> "$tag: "
|
||||
}
|
||||
|
||||
val stackTrace = ex?.dump()
|
||||
|
||||
// 本文とスタックトレース
|
||||
val sb = StringBuilder()
|
||||
val message = messageArg?.takeIf { it.isNotEmpty() }?.also { sb.append(it) }
|
||||
if (stackTrace != null) {
|
||||
if (message != null) sb.append("\n")
|
||||
sb.append(stackTrace)
|
||||
}
|
||||
|
||||
splitLines(prefix, sb) {
|
||||
printlnOrWtf(priority, it.toString())
|
||||
}
|
||||
// LogEntity.saveLog(priority, tag, message, stackTrace)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(msg: String) = log(Log.VERBOSE, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun d(msg: String) = log(Log.DEBUG, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun i(msg: String) = log(Log.INFO, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun w(msg: String) = log(Log.WARN, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun e(msg: String) = log(Log.ERROR, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun wtf(msg: String) = log(Log.ASSERT, messageArg = msg)
|
||||
|
||||
@JvmStatic
|
||||
fun recordFirebaseCrashlytics(ex: Throwable?) {
|
||||
try {
|
||||
ex ?: return
|
||||
// FirebaseCrashlytics.getInstance().recordException(ex)
|
||||
} catch (ignored: Throwable) {
|
||||
// 単体テストで FirebaseCrashlytics.getInstance() が例外を出す
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun i(ex: Throwable?, msg: String? = null) {
|
||||
recordFirebaseCrashlytics(ex)
|
||||
log(Log.INFO, ex = ex, messageArg = msg)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun w(ex: Throwable?, msg: String? = null) {
|
||||
recordFirebaseCrashlytics(ex)
|
||||
log(Log.WARN, ex = ex, messageArg = msg)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun e(ex: Throwable?, msg: String? = null) {
|
||||
recordFirebaseCrashlytics(ex)
|
||||
log(Log.ERROR, ex = ex, messageArg = msg)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun wtf(ex: Throwable?, msg: String? = null) {
|
||||
recordFirebaseCrashlytics(ex)
|
||||
log(Log.ASSERT, ex = ex, messageArg = msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package jp.juggler.pushreceiverapp
|
||||
|
||||
import jp.juggler.util.data.Base128.decodeBase128
|
||||
import jp.juggler.util.data.Base128.encodeBase128
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class Base128Test {
|
||||
|
||||
@Test
|
||||
fun useBase128() {
|
||||
for (len in 0..20) {
|
||||
for (i in 0 until 256) {
|
||||
val orig = ByteArrayOutputStream(32)
|
||||
.apply {
|
||||
repeat(len) {
|
||||
write(i)
|
||||
}
|
||||
}.toByteArray()
|
||||
val encoded = orig.encodeBase128()
|
||||
val decoded = encoded.decodeBase128()
|
||||
assertArrayEquals(
|
||||
"len=$len,i=$i",
|
||||
orig,
|
||||
decoded
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package jp.juggler
|
||||
|
||||
import jp.juggler.util.data.BinPackList
|
||||
import jp.juggler.util.data.BinPackMap
|
||||
import jp.juggler.util.data.decodeBinPack
|
||||
import jp.juggler.util.data.encodeBinPack
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class BinPackTest {
|
||||
@Test
|
||||
fun testTypes() {
|
||||
fun ByteArray.dump() = joinToString(" ") { "%d".format(it) }
|
||||
|
||||
fun encodeDecode(v: Any?, expected: Any? = v) {
|
||||
val encoded = v.encodeBinPack()
|
||||
val decoded = encoded.decodeBinPack()
|
||||
val message = "($v ${v?.javaClass?.simpleName}) dump=${encoded.dump()}"
|
||||
when {
|
||||
expected is ByteArray -> assertArrayEquals(
|
||||
"${v?.javaClass?.simpleName} $v",
|
||||
expected,
|
||||
decoded as? ByteArray
|
||||
)
|
||||
v is Set<*> -> {
|
||||
val decodedSet = (decoded as? BinPackList)?.toSet()
|
||||
assertNotNull("$message decoded?", decodedSet)
|
||||
assertEquals("$message same size", v.size, decodedSet!!.size)
|
||||
assertTrue("$message containsAll 1", v.containsAll(decodedSet))
|
||||
assertTrue("$message containsAll 2", decodedSet.containsAll(v))
|
||||
}
|
||||
else -> assertEquals(
|
||||
message,
|
||||
expected,
|
||||
decoded
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
encodeDecode(null)
|
||||
encodeDecode(true)
|
||||
encodeDecode(false)
|
||||
encodeDecode(ByteArray(0))
|
||||
encodeDecode(ByteArray(1) { it.toByte() })
|
||||
encodeDecode(ByteArray(3) { it.toByte() })
|
||||
encodeDecode("")
|
||||
encodeDecode("日本語")
|
||||
encodeDecode(emptyArray<Any?>(), BinPackList())
|
||||
encodeDecode(emptyList<Any?>(), BinPackList())
|
||||
encodeDecode(emptySet<Any?>(), BinPackList())
|
||||
encodeDecode(arrayOf<Any?>(null), BinPackList(null))
|
||||
encodeDecode(listOf<Any?>(null), BinPackList(null))
|
||||
encodeDecode(setOf<Any?>(null))
|
||||
encodeDecode(arrayOf("a"), BinPackList("a"))
|
||||
encodeDecode(listOf("a"), BinPackList("a"))
|
||||
encodeDecode(setOf("a"))
|
||||
encodeDecode(emptyMap<Any?, Any?>(), BinPackMap())
|
||||
encodeDecode(mapOf<Any?, Any?>(null to null), BinPackMap(null to null))
|
||||
encodeDecode(mapOf<Any?, Any?>(1 to 1), BinPackMap(1 to 1))
|
||||
|
||||
fun doubleStepSequence(start: Double, endInclusive: Double, step: Double) =
|
||||
sequence {
|
||||
var v = start
|
||||
while (true) {
|
||||
yield(v)
|
||||
val newValue = v + step
|
||||
// 範囲を超えたか、オーバーフローで同じ値になるか
|
||||
if (newValue > endInclusive || newValue == v) break
|
||||
v = newValue
|
||||
}
|
||||
}
|
||||
|
||||
fun floatStepSequence(start: Float, endInclusive: Float, step: Float) =
|
||||
sequence {
|
||||
var v = start
|
||||
while (true) {
|
||||
yield(v)
|
||||
val newValue = v + step
|
||||
// 範囲を超えたか、オーバーフローで同じ値になるか
|
||||
if (newValue > endInclusive || newValue == v) break
|
||||
v = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// - ビット数の多い数値型は適当に端折るが、下位ビットが毎回同じにならないよう stepを工夫する
|
||||
|
||||
encodeDecode(0.0)
|
||||
encodeDecode(Double.NaN)
|
||||
encodeDecode(Double.NEGATIVE_INFINITY)
|
||||
encodeDecode(Double.POSITIVE_INFINITY)
|
||||
encodeDecode(Double.MIN_VALUE) // nealy 0 positive value
|
||||
encodeDecode(-Double.MAX_VALUE) // negative min end
|
||||
encodeDecode(Double.MAX_VALUE)
|
||||
run {
|
||||
var callCount = 0
|
||||
for (n in doubleStepSequence(
|
||||
-Double.MAX_VALUE,
|
||||
Double.MAX_VALUE,
|
||||
Double.MAX_VALUE / (256 - 7 - Double.MIN_VALUE)
|
||||
)) {
|
||||
encodeDecode(n)
|
||||
++callCount
|
||||
}
|
||||
assertEquals("callCount", 498, callCount)
|
||||
}
|
||||
|
||||
encodeDecode(0f)
|
||||
encodeDecode(Float.NaN)
|
||||
encodeDecode(Float.NEGATIVE_INFINITY)
|
||||
encodeDecode(Float.POSITIVE_INFINITY)
|
||||
encodeDecode(Float.MIN_VALUE) // nealy 0 positive value
|
||||
encodeDecode(-Float.MAX_VALUE) // negative min end
|
||||
encodeDecode(Float.MAX_VALUE)
|
||||
run {
|
||||
var callCount = 0
|
||||
for (n in floatStepSequence(
|
||||
-Float.MAX_VALUE,
|
||||
Float.MAX_VALUE,
|
||||
Float.MAX_VALUE / (256 - 7 - Float.MIN_VALUE)
|
||||
)) {
|
||||
encodeDecode(n)
|
||||
++callCount
|
||||
}
|
||||
assertEquals("callCount", 499, callCount)
|
||||
}
|
||||
|
||||
// - ByteとShortにはIntRangeと同等のクラスがない
|
||||
|
||||
encodeDecode(0.toByte())
|
||||
encodeDecode(Byte.MIN_VALUE)
|
||||
encodeDecode(Byte.MAX_VALUE)
|
||||
for (n in Byte.MIN_VALUE.toInt()..Byte.MAX_VALUE.toInt()) {
|
||||
encodeDecode(n.toByte())
|
||||
}
|
||||
|
||||
encodeDecode(0.toShort())
|
||||
encodeDecode(Short.MIN_VALUE)
|
||||
encodeDecode(Short.MAX_VALUE)
|
||||
for (n in Short.MIN_VALUE.toInt()..Short.MAX_VALUE.toInt() step 1.shl(Short.SIZE_BITS - 8) - 7) {
|
||||
encodeDecode(n.toShort())
|
||||
}
|
||||
|
||||
encodeDecode(0.toChar())
|
||||
encodeDecode(Char.MIN_VALUE)
|
||||
encodeDecode(Char.MAX_VALUE)
|
||||
for (n in Char.MIN_VALUE..Char.MAX_VALUE step 1.shl(Char.SIZE_BITS - 8) - 7) {
|
||||
encodeDecode(n)
|
||||
}
|
||||
|
||||
encodeDecode(0)
|
||||
encodeDecode(Int.MIN_VALUE)
|
||||
encodeDecode(Int.MAX_VALUE)
|
||||
for (n in Int.MIN_VALUE..Int.MAX_VALUE step 1.shl(Int.SIZE_BITS - 8) - 7) {
|
||||
encodeDecode(n)
|
||||
}
|
||||
|
||||
encodeDecode(0.toLong())
|
||||
encodeDecode(Long.MIN_VALUE)
|
||||
encodeDecode(Long.MAX_VALUE)
|
||||
for (n in Long.MIN_VALUE..Long.MAX_VALUE step 1L.shl(Long.SIZE_BITS - 8) - 7) {
|
||||
encodeDecode(n)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
package jp.juggler
|
||||
|
||||
import jp.juggler.crypt.*
|
||||
import jp.juggler.util.data.decodeBase64
|
||||
import jp.juggler.util.data.decodeJsonObject
|
||||
import jp.juggler.util.log.AdbLog
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import java.security.interfaces.ECPrivateKey
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class WebPushCryptTest {
|
||||
|
||||
// https://developers.google.com/web/updates/2016/03/web-push-encryption
|
||||
|
||||
@Test
|
||||
fun testGenerateKeyPair() {
|
||||
val provider = defaultSecurityProvider
|
||||
val pair = provider.generateKeyPair()
|
||||
val privateKey = (pair.private as ECPrivateKey)
|
||||
val bytes = encodePrivateKeyRaw(privateKey)
|
||||
val newKey = provider.decodePrivateKeyRaw(bytes)
|
||||
assertEquals(
|
||||
"s is same?",
|
||||
privateKey.s,
|
||||
newKey.s,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decryptMessage() {
|
||||
// Authentication secret (auth_secret)
|
||||
// 購読時に指定する
|
||||
val authSecret = "BTBZMqHH6r4Tts7J_aSIgg".decodeBase64()
|
||||
|
||||
// User agent public key (ua_public)
|
||||
// 購読時に指定する
|
||||
val receiverPublicBytes =
|
||||
"BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4".decodeBase64()
|
||||
|
||||
// User agent private key (ua_private)
|
||||
val receiverPrivateBytesShort = "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94".decodeBase64()
|
||||
|
||||
// encryption ヘッダから
|
||||
val saltBytes = "pr1_1DFjrzX3RNvJPRngDA".decodeBase64()
|
||||
|
||||
// Application server public key (as_public)
|
||||
// crypto-key ヘッダの前半、dh=xxx; の中味
|
||||
// crypto-key: dh=BLJQjupjQyujhTC--_5xRUgcBfAP_zINsAGTlaDuEME7s9TVgQYsyrzrgbt1vqScmZkoj4BWfPit6EzzaXDW02I;p256ecdsa=BDmWlrZ3gvcv0R7sBhaSp_99FRSC3bBNn9CElRvbcviwYwVPL1Z-G9srAJS6lv_pMe5IkTmKgBWUCNefnN3QoeQ
|
||||
val senderPublicBytes =
|
||||
"BLJQjupjQyujhTC--_5xRUgcBfAP_zINsAGTlaDuEME7s9TVgQYsyrzrgbt1vqScmZkoj4BWfPit6EzzaXDW02I"
|
||||
.decodeBase64()
|
||||
|
||||
val body =
|
||||
"pTTuh1jT8KJ4zaGwIWjg417KTDzh+eIVe472nMgett3XyhoM5pAz8Yu2RPBXJHE/AojoMA1g+/uzbByu3d1/AygBh99qJ6Xtjya+XBSYoVrNJqT7vq0cKU9bZ8NrEepnaZUc2HjFUDDXNyHi2xBtJnMk/hSZTzyaiCQS2KssGAwixgdK/dTP8Yg+Pul3tgOQvq5CbYFd7iwBQntVv80vO8X+5hyIglA21+6/2fq5lCZSMri5K9/WbSb6erLkxO//A92KjZTnuufE4pUwtIdYW1bFnw5xu6ozjsCsDLbQTSo+JmghOzc/iYx5hG+y5YViC1UXue4eKKlmjbVDRLH6WkEEIKH2cwd4Gf9ewhYwhH7oKKIc4tjvRunq2gtBirQgRYJahgfwykdYA44iyogBc1rFZPGbxr1ph4RxVhdBmIZ+yMN6GQSiDCS+8jKGsc5xnjxrSXXdFva1a2xc1lpiReypZlTTXFmF16Cf+Z6B0UvFTa2AcqEDD0BBlhhbMBoG7n4CRjr5ObE2lG5PBg+gqitx/O1S+X8a4N78L+eK1upEVM+HRQAdCmiqDNJF0/N/VWSMrNCl7HNgnhmYU9Z1aYepiEioz1Tu14UzY/2NOx5z4h4szyJW8s/diAyOhnh+RBRM3QLHtygpLZ3i7o6vVUc="
|
||||
.decodeBase64()
|
||||
|
||||
AesGcmDecoder(
|
||||
// receiver private key in X509 format
|
||||
receiverPrivateBytes =
|
||||
defaultSecurityProvider.decodePrivateKeyRaw(receiverPrivateBytesShort).encoded,
|
||||
// receiver public key in 65bytes X9.62 uncompressed format
|
||||
receiverPublicBytes = receiverPublicBytes,
|
||||
// sender public key in 65bytes X9.62 uncompressed format
|
||||
senderPublicBytes = senderPublicBytes,
|
||||
// auth secrets created at subscription
|
||||
authSecret = authSecret,
|
||||
// salt in HTTP header
|
||||
saltBytes = saltBytes,
|
||||
).run {
|
||||
assertEquals(
|
||||
"sharedKeyBytes",
|
||||
"irnQ9JOfMP/kl/SB8LUHpvjmUjwlkYzypisDnlHVKSA=",
|
||||
sharedKeyBytes.encodeBase64()
|
||||
)
|
||||
|
||||
deriveKey()
|
||||
|
||||
assertEquals(
|
||||
"ikm",
|
||||
"Tq5bGvBQUWTQdqFfAK1WAE/etlIpcc07QLh+RNCAD9Y=",
|
||||
ikm.encodeBase64()
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"contentEncryptionKey",
|
||||
"patR3W6rY0PG/5YnwY3/kA==",
|
||||
key.encodeBase64()
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"nonce",
|
||||
"E/GxDDwW9lfa7/by",
|
||||
nonce.encodeBase64()
|
||||
)
|
||||
decode(body.toByteRange())
|
||||
}.decodeUTF8().let {
|
||||
assertEquals(
|
||||
"text",
|
||||
"""{"title":"あなたのトゥートが tateisu 🤹 さんにお気に入り登録されました","image":null,"badge":"https://mastodon2.juggler.jp/badge.png","tag":84,"timestamp":"2018-05-11T17:06:42.887Z","icon":"/system/accounts/avatars/000/000/003/original/72f1da33539be11e.jpg","data":{"content":":enemy_bullet:","nsfw":null,"url":"https://mastodon2.juggler.jp/web/statuses/98793123081777841","actions":[],"access_token":null,"message":"%{count} 件の通知","dir":"ltr"}}""",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 今どきのMastodonはCrypto-Keyが異なる
|
||||
* dh=XXX;p256ecdsa=XXX
|
||||
*/
|
||||
@Test
|
||||
fun test2() {
|
||||
// 購読時に分かった情報
|
||||
val receiverPrivateBytes =
|
||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN1UVSn5P4XC9zuuT3sW8TTikA8AhsjZKp9W6BwzSlW2hRANCAAQSFVIOe_wsdFuDCKjrNRM1yWIkwlfx8ZoQ3OyYJ2K5oeXpmjo5EAimVq0Fs0NuxA8Y6F1hTB_Orc4gR1WOK-W4"
|
||||
.decodeBase64()
|
||||
val receiverPublicBytes =
|
||||
"BBIVUg57_Cx0W4MIqOs1EzXJYiTCV_HxmhDc7JgnYrmh5emaOjkQCKZWrQWzQ27EDxjoXWFMH86tziBHVY4r5bg"
|
||||
.decodeBase64()
|
||||
val senderPublicBytesOld =
|
||||
"BLzIgsz-VRhTxuVgNoQljTwAFzxbanfGxNk8tldaruBztvsK9elES_2lE_8c91-RNOInEBEFUrCzDw-60bzUCr8"
|
||||
.decodeBase64()
|
||||
val authSecret = "WrgqiWu8r3D9Ql3qYGkXTw"
|
||||
.decodeBase64()
|
||||
|
||||
// プッシュメッセージに含まれる情報
|
||||
val headerJson = """{
|
||||
|"Digest":"SHA-256=kN1u9yotvg3utprFOaJ4qUGJ6cRhxGNfICXAGLjx7uM=",
|
||||
|"Content-Encoding":"aesgcm",
|
||||
|"Encryption":"salt=xtC3F3tO3UvQBtq-4Q38AQ",
|
||||
|"Crypto-Key":"dh=BLivX-rukpGqww9YMqavS7o112MNTobqBqjzPX1ioUojdrDHKM1DwZKn6U2au0ddohfC4BGTDatjF96S7dOP2C8;p256ecdsa=BLzIgsz-VRhTxuVgNoQljTwAFzxbanfGxNk8tldaruBztvsK9elES_2lE_8c91-RNOInEBEFUrCzDw-60bzUCr8",
|
||||
|"Authorization":"WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL21hc3RvZG9uLW1zZy5qdWdnbGVyLmpwIiwiZXhwIjoxNjc0ODQ3MjQ4LCJzdWIiOiJtYWlsdG86dGF0ZWlzdStqdWdnbGVyQGdtYWlsLmNvbSJ9.ksn_40KkHNcS0G3C8jMG-dsA6AcUydYYc1sA8JyXCCuDrNr-CLnl_-ezk1s-pC75yukNN0pIfqPJlJYtY_MQoQ",
|
||||
|"<>cameFrom":"unifiedPush"
|
||||
|}""".trimMargin().decodeJsonObject()
|
||||
|
||||
val body =
|
||||
"4BVejMFrVADAEzkonO1QqUnDLiWCGAlzSbWg1KkG23BMZNLxUcQPJtULTTEMdoIzg4zZwDmsanDp5fepH8BDKpb5IoQlyFJjN0pq3tebWj79QL9h7QafxPByGx9HF9-zKlOUbmPMc3yS1tfccz30HvVSsMNUtcpuy1hsPkknwRBPI_SoLN20LEtAQ6IKLQGpiNMDx2rND9bd9wLz0Cr4pxUgZVxv0VjpiKHyTup0_BRotGd5e719uMmyDE-hzrqQYRr2QFrvHa1tNk41tpbPpeSAqhihsxU6bXxc4Lbws1Q6DyZBYHSUr6Av4SldA2Kmi3EjLijRvz3YEDWnSGv65V0SJv-O7tmFjexqFmFcF0qfv4Udzl9prjmutztcaWRAH-v9JdP3o2kthpK8RM4NC6Y0yU3GhEQ0z_1cfsuWsAvRe3H8pVcx3GXHVdWJ7VfqAwI-anSZz355-cuBbxAyrsBSp1Ysfru2D_5g53SZ7iNduHsXfKno9e6BkQr6u5pleXVHpRHYimzsjj46wbA0IfvEkhBpp9CoJ3JzPFkBowxpzuzmN1_IDiSDoYWVMYl1GXtwWOmKMKaKOCh3ULk2FH1WdUzNlvMd1N4Kf-MXjHFhkE33jSlLnKRLHqgzDEJgofWEzfoV-zruHFQfKwiN_7_NJdLYuGVV3z0IFiw6g9RdIvrNy7OEsTCaXbWC-xOEelJAvALvu308t_0PxxRdhSLVivrubyLZEKz1R8d6MzJ9b3f_mg6oYiGJ1u5oMrUvUUeNCERq4t9f0WMLo-zLFkNXvGe2TxnKMO9ZsWlXS1OnKnN6olNPKfVAFW50RxD6nBdP3S2a9uI58D9ycanCU_-i8osFrKg-GbYZ2tvRZw"
|
||||
.decodeBase64()
|
||||
|
||||
// Encryption からsaltを読む
|
||||
val saltBytes = headerJson.string("Encryption")?.parseSemicolon()
|
||||
?.get("salt")?.decodeBase64()
|
||||
?: error("missing Encryption.salt")
|
||||
|
||||
// Crypt-Key から dh と p256ecdsa を見る
|
||||
val cryptKeys = headerJson.string("Crypto-Key")?.parseSemicolon()
|
||||
?: error("missing Crypto-Key")
|
||||
|
||||
val senderPublicBytes = cryptKeys["dh"]?.decodeBase64()
|
||||
?: senderPublicBytesOld
|
||||
|
||||
// JWT検証で使われるらしい
|
||||
// val p256ecdsa = cryptKeys["p256ecdsa"]?.decodeBase64()
|
||||
// ?: error("missing p256ecdsa")
|
||||
|
||||
AesGcmDecoder(
|
||||
// receiver private key in X509 format
|
||||
receiverPrivateBytes = receiverPrivateBytes,
|
||||
// receiver public key in 65bytes X9.62 uncompressed format
|
||||
receiverPublicBytes = receiverPublicBytes,
|
||||
// sender public key in 65bytes X9.62 uncompressed format
|
||||
senderPublicBytes = senderPublicBytes,
|
||||
// auth secrets created at subscription
|
||||
authSecret = authSecret,
|
||||
// salt in HTTP header
|
||||
saltBytes = saltBytes,
|
||||
).run {
|
||||
deriveKey()
|
||||
decode(body.toByteRange())
|
||||
}.decodeUTF8().let {
|
||||
assertEquals(
|
||||
"text",
|
||||
"""{"access_token":"ON0yBbjmRS-QuSk5Uv7fjeKFVQESnYCZP39z71On68E","preferred_locale":"ja","notification_id":341159,"notification_type":"favourite","icon":"https://m1j.zzz.ac/accounts/avatars/000/008/939/original/112ed1e5343f2e7b.png","title":"tateisu⛏️@テスト鯖 :ct080:さんにお気に入りに登録されました","body":"クライアントアプリにAPIキーとシークレットを持たせるという考え方は、クライアント側でシークレットが如何に漏洩しやすいかを考えると悪手としかいいようがない。簡単に漏洩して、アプリと無関係なbotに流用/悪用される"}""",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/web-push-libs/ecec/blob/master/src/decrypt.c
|
||||
|
||||
@Test
|
||||
fun testRfc8188() {
|
||||
val body = "I1BsxtFttlv3u_Oo94xnmwAAEAAA-NAVub2qFgBEuQKRapoZu-IxkIva3MEB1PD-ly8Thjg"
|
||||
.decodeBase64()
|
||||
val inputKey = "yqdlZ-tYemfogSmv7Ws5PQ"
|
||||
.decodeBase64()
|
||||
|
||||
Aes128GcmDecoder(body.byteRangeReader()).run {
|
||||
AdbLog.i("recordSize=$recordSize, keyId.size=${keyId.size}")
|
||||
|
||||
assertEquals(
|
||||
"salt",
|
||||
"I1BsxtFttlv3u_Oo94xnmw",
|
||||
salt.encodeBase64Url()
|
||||
)
|
||||
assertEquals(
|
||||
"keyId",
|
||||
"",
|
||||
keyId.encodeBase64Url()
|
||||
)
|
||||
|
||||
deriveKeyRfc8188(inputKey.toByteRange())
|
||||
|
||||
assertEquals(
|
||||
"prk",
|
||||
"zyeH5phsIsgUyd4oiSEIy35x-gIi4aM7y0hCF8mwn9g",
|
||||
prk.encodeBase64Url()
|
||||
)
|
||||
assertEquals(
|
||||
"key",
|
||||
"_wniytB-ofscZDh4tbSjHw",
|
||||
key.encodeBase64Url()
|
||||
)
|
||||
assertEquals(
|
||||
"NONCE",
|
||||
"Bcs8gkIRKLI8GeI8",
|
||||
nonce.encodeBase64Url()
|
||||
)
|
||||
decode()
|
||||
}.decodeUTF8().let {
|
||||
assertEquals(
|
||||
"text",
|
||||
"I am the walrus",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRfc8188b() {
|
||||
val inputKey = "BO3ZVPxUlnLORbVGMpbT1Q"
|
||||
.decodeBase64()
|
||||
|
||||
val rawBody =
|
||||
"uNCkWiNYzKTnBN9ji3-qWAAAABkCYTHOG8chz_gnvgOqdGYovxyjuqRyJFjEDyoF1Fvkj6hQPdPHI51OEUKEpgz3SsLWIqS_uA"
|
||||
.decodeBase64()
|
||||
|
||||
Aes128GcmDecoder(rawBody.byteRangeReader()).run {
|
||||
AdbLog.i("recordSize=$recordSize, keyId.size=${keyId.size}, encryptedContent.size=${encryptedContent.size}")
|
||||
deriveKeyRfc8188(inputKey.toByteRange())
|
||||
decode()
|
||||
}.decodeUTF8().let {
|
||||
assertEquals(
|
||||
"text",
|
||||
"I am the walrus",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMisskey13() {
|
||||
val receiverPrivateBytes =
|
||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgr18ZyNLsRg71vHNxBBGFxeIvV497YUaONPJL1EOKLGChRANCAAQUrMEJuCxzjD-S3xDlcgAvmnbKQZVQzkTCkOcKLSHgGopshtc3ETyrD7m7fhuZAlV1KXJVqHVBtMwrwvZ5xIyw"
|
||||
.decodeBase64()
|
||||
val receiverPublicBytes =
|
||||
"BBSswQm4LHOMP5LfEOVyAC-adspBlVDORMKQ5wotIeAaimyG1zcRPKsPubt-G5kCVXUpclWodUG0zCvC9nnEjLA"
|
||||
.decodeBase64()
|
||||
// val senderPublicBytes =
|
||||
// "BBtZLosjNMsuFAQg0QHYIgDNKG6IC_yxMYW1Tx_Cx20FbqEbAt_KN56zLc48yJxmOJhMkjR5Kf68bEIugNQ-wWU"
|
||||
// .decodeBase64()
|
||||
val authSecret = "x6yts9DKC4ZnnHdPHgiwkQ"
|
||||
.decodeBase64()
|
||||
|
||||
// val headerJson = """{
|
||||
// |"TTL":"2419200",
|
||||
// |"Content-Encoding":"aes128gcm",
|
||||
// |"Authorization":"vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL21hc3RvZG9uLW1zZy5qdWdnbGVyLmpwIiwiZXhwIjoxNjc0OTU4MTMyLCJzdWIiOiJodHRwczovL2RyZHIuY2x1YiJ9.I9xIycRJXiWYTZQlsTiS5RyceJNXGbrT8N-rac7Q5UouTWnZUmNbxEknOQigZV71qY3DJ_S7RomXJW3p1KwNmQ, k=BBtZLosjNMsuFAQg0QHYIgDNKG6IC_yxMYW1Tx_Cx20FbqEbAt_KN56zLc48yJxmOJhMkjR5Kf68bEIugNQ-wWU",
|
||||
// |"<>cameFrom":"fcm"
|
||||
// |}""".trimMargin()
|
||||
val rawBody =
|
||||
"0VUgniuATYrYU481rjSNBAAAEABBBK66EO_qZXsRw0Clzv0LF-SxMe5qRmCgKrZHApIc5ZX_XsXmzBg1rU827Hp9BSdXEWaIaBYAYROjTQfSuHtylP1H0_EOEMNdqswJpEVdVVQrfC_7JAxNAOlXUz4QU3oKl3yhSTlp6M4kFFaBfPrn2SyFI4f5w0wIRATH-Ck0shakAp5hKD7nVwvzEmh3wg8Vug8gPRmGhOPet6DzG5rhLtPng5OJ2eFQUB00duVZyW8TgZ8iPd0GYZkaHw2b16OBN3doLPKiYN_OL-JKVmWutMzivG52DDQ_XqbaFfu5dRLWHkyipq078-nDL6yw9_tNQCdDcVGKx1llxHI6FuPbYgF_RJ8PNZ1Sj-sMRdGtQqcsFH0WDWJu_JBqHSYvBxjEhhDv4qepQTYDl3MYVI-EIbQLIWuCZRo4FRkd594endP6IQ2LH950EE4AMeVHmdxENGELJidrF4eE8reHWf5We0_Jj1m92L8ZJ--z_FMHdOEGlCHwFp1IQPNS-SF6CPZVdrzxjLMo12CELNjh1g9bKxA2ld7OBYcVb3nt6uWBBgiJEqSXEVjzv2JhoHMh3lHLyFngoddfL55aDEVe4gTOwhflPSiKO-8nQd87KLQ_vi4r-SItRzQXhXcI0v7ryFubBwWQrRmRaIaDrMgheEJ50caWgeaDoTXtgkorG63q6KIfkpBlyDyq3YaKpXHJ7rmr-1BYP0Z8PS2O5bqRFyLgtLXIn5a3da_XlTnjdHJH7_N0c8WxVRow5Z5Qzlj790FuGrMQQ8c6SrAC7lTmt6_wq-Ej3SH53jN0HumZ7v4pmkqCR5o7SJYHAgMx9KUeW-Rwr9k3XKVS9MMP9wPhke0Iy51LOfDZ_U6tHVQ_QEkEb1lk1eNSNo0ZSna1vhXh5WB8Pnm3Skxa1k4qvQFKjdNFOZZ57lXqG42oEmplZirhiBgvf40sWmFs_s6_u4tTkD1jagHlrus8l7U5T-34RpzOeqM3z5wqjylBJyubwfEA_7C3WWYOw5U0bduP7QLgFGGzVmu3qAAcQgfHOOehgWXc95j50jXjfV9XD3znXX3WLY0R4qdkWcivQMSxL3PATh1a9fgZJs-SRvH7fW5SrEK3Cj9wAXLfOOcL9gP519eW4sPN-P3xupAfgxXblolBvx1wmdtL51ey1X02cOK_EyFS0pl5D_1vaL6ncq6RDfP2emD-U4jSBEOJbWpzINr9xUnRBLxGYbRZfSSqjdFZGO49JKOW_h9vtZT-NAuPaar0v0Qfz_qF5_-pyo8dQpfNGxZ1ZTzlMwy4v6l18RRAZx2smbgNVmSe2uUCDhcFhO_Y9a9wlbbhYUesenR0g-fbUvVGSdz3V2hoTanQJLwOSbigIFmgMTUnOGPs4uv4ynXleDR6h6zoZ5WwuR8i06AjYq9M5E3nlmxoGKebX_6r1p0Fg0R8QRiz9UmRtxn-G72eLEy5351ZA4xEZrvm4eYoyS7t3BRta0R_4qKFdNONv46ISIj3TU3sUNo2ktIbZE8VjDomxqfgLugbx1Oyvipa9xTFyaJBzhSxxwfdzLlfxdLdrGoh2OseBY56_WR99yh5x6q2UQDo6lyqjzIa8nY_EptfqQmzgCvUJYGmRqXi6Cp5bEyuUvxZ_W-paJoMT5ZW_IjVrSkqYm-oxOkGzKsaAjLmA0e9Qek7uZlLZ9KPl56ON7AHqli0qHi5ZaoosIMVMd6GN0DUfiFHpimGmHw63W0xwArEr0sWf3Wqclk0b3BS7o4oRPMjtjersoFtqxaJ8xJpV4c3UwNjXz9eBQFtJZ_NR1jv_4Kuet3d78RojWEo4OGP9Y7VbPc77yekqm1f1Fxi68w-aZFgL6ClDsy1eAH5HQmYa5KzylTYOt3ZQDXGARiFXR3N0isKPsBBcXZkIzcrKEccfBhYxm2ru3ruNnwMZ4XKzmq5SLRfhxvsCspPaiW3N7oKEbX86ONoAL_i6kcpxAn8iY_XZ36ZyOD_cDkG0OesIdbc4hRcXaLSWmvknXy1KUCbwbe9pMx7HiYn5u_8vgtSELlWQdagc2T3SiD8-gqXTp3RmwwAJSizme2XL9RcjkZOtQnItpNbCwON5afYM85OEKFQkatnt6d8rm2WhoZZ8lU03zD9Kt2Tn9vbkhDYcy_ZE0RHERQLtl5Q4GT81J8RpOEjeJTt2egGFI0PC_tx5MNl_Piy6ows7ly24TsEjNoDJQpnowk_S4OLbN2fhpr1-yhfhbQ-j447FcYkEACjW5t74X4ey8iT88rVMmgefDx7o7NYWW7aKd6vfPpe7EmYiERxCDPYhzQhuKk8vm3FoWM2C6cuuzq1ElUraA70SM9smuwOBJFqgs7ZosIjhb7BdrMiy8lILejHKkTyl-lbhKNzGvO4EIgeHZRn8vzdDmwIINQskT3KgPMpXw"
|
||||
.decodeBase64()
|
||||
|
||||
Aes128GcmDecoder(rawBody.byteRangeReader()).run {
|
||||
deriveKeyWebPush(
|
||||
// receiver private key in X509 format
|
||||
receiverPrivateBytes,
|
||||
// receiver public key in 65bytes X9.62 uncompressed format
|
||||
receiverPublicBytes,
|
||||
// auth secrets created at subscription
|
||||
authSecret,
|
||||
)
|
||||
decode()
|
||||
}.decodeUTF8().let {
|
||||
assertEquals(
|
||||
"text",
|
||||
"""{"type":"notification","body":{"id":"9ajlf15org","createdAt":"2023-01-28T14:08:50.844Z","type":"reaction","isRead":false,"userId":"8fhziu1e3v","user":{"id":"8fhziu1e3v","name":"tateisu⛏️@テスト鯖 :ct080:","username":"tateisu","host":"mastodon2.juggler.jp","avatarUrl":"https://m2j.zzz.ac/accounts/avatars/000/000/001/original/e75c1f2674f44551.png","avatarBlurhash":"yDHUW|V]02t30otQ_H?XoeD,WBt7jbt2tMocN2Rn%Fk9nTE1kB%2e:X3bFRVR:ayWBj@%0fkRR?Ej[E3axt7j]ob9Oay%Fj[xajGIW","isBot":true,"isCat":false,"instance":{"name":"ジャグ鯖(テスト用)","softwareName":"mastodon","softwareVersion":"3.5.3","iconUrl":"https://mastodon2.juggler.jp/packs/media/icons/android-chrome-36x36-d64a2e909b4574fb02214762c513032d.png","faviconUrl":"https://mastodon2.juggler.jp/packs/media/icons/favicon-48x48-28b6709639acd18287a854f974311336.png","themeColor":"#6364ff"},"emojis":{"ctf00":"https://m2j.zzz.ac/custom_emojis/images/000/007/937/original/ctf00.png","ct080":"https://m2j.zzz.ac/custom_emojis/images/000/004/225/original/ct080.png","ctff0":"https://m2j.zzz.ac/custom_emojis/images/000/008/177/original/ctff0.png"},"onlineStatus":"unknown"},"note":{"id":"99tzth0zfn","createdAt":"2023-01-10T16:09:58.643Z","userId":"99tcttijnu","text":":thinking_woozy_portal_orange::thinking_garbled:","visibility":"public","localOnly":false,"renoteCount":0,"repliesCount":2,"reactions":{"👍":1,":revbunhdthinking@.:":1,":ainers_misskeyio@misskey.io:":1},"reactionEmojis":{"ainers_misskeyio@misskey.io":"https://s3.arkjp.net/misskey/ea176d9b-a92a-4ec2-b11c-defdac551156.apng"},"fileIds":[],"files":[],"replyId":null,"renoteId":null,"myReaction":":revbunhdthinking@.:"},"reaction":"👍"},"userId":"99tcttijnu","dateTime":1674914932947}""",
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
66
build.gradle
66
build.gradle
|
@ -1,37 +1,36 @@
|
|||
buildscript {
|
||||
|
||||
ext.jvm_target = "1.8"
|
||||
|
||||
ext.min_sdk_version = 26
|
||||
ext.target_sdk_version = 33
|
||||
ext.compile_sdk_version = 33
|
||||
ext.build_tools_version = "33.0.1"
|
||||
|
||||
ext.desugar_lib_bersion = "1.2.0"
|
||||
ext.startup_version = "1.1.1"
|
||||
ext.roomVersion = "2.5.0"
|
||||
ext.workVersion = "2.7.1"
|
||||
ext.conscryptVersion = "2.5.2"
|
||||
ext.androidxTestVersion = "1.5.0"
|
||||
ext.ankoVersion = "0.10.8"
|
||||
ext.appcompatVersion = "1.6.0"
|
||||
ext.archVersion = "2.1.0"
|
||||
ext.composeVersion = "1.0.5"
|
||||
ext.conscryptVersion = "2.5.2"
|
||||
ext.desugarLibVersion = "1.2.0"
|
||||
ext.detektVersion = "1.22.0"
|
||||
ext.glideVersion = "4.14.2"
|
||||
|
||||
ext.appcompat_version = "1.6.0"
|
||||
ext.lifecycle_version = "2.5.1"
|
||||
ext.arch_version = "2.1.0"
|
||||
|
||||
ext.junitVersion = "4.13.2"
|
||||
ext.kotlinJvmTarget = "1.8"
|
||||
ext.koinVersion = "3.1.3"
|
||||
ext.kotlinVersion = "1.8.0"
|
||||
ext.kotlinxCoroutinesVersion = "1.6.4"
|
||||
ext.lifecycleVersion = "2.5.1"
|
||||
ext.okhttpVersion = "4.10.0"
|
||||
|
||||
ext.kotlin_version = "1.7.21"
|
||||
ext.kotlinx_coroutines_version = "1.6.4"
|
||||
|
||||
ext.anko_version = "0.10.8"
|
||||
ext.junit_version = "4.13.2"
|
||||
|
||||
ext.detekt_version = "1.22.0"
|
||||
|
||||
ext.compose_version = "1.0.5"
|
||||
|
||||
ext.koin_version = "3.1.3"
|
||||
|
||||
ext.androidx_test_version = "1.5.0"
|
||||
ext.roomVersion = "2.5.0"
|
||||
ext.stBuildToolsVersion = "33.0.1"
|
||||
ext.stCompileSdkVersion = 33
|
||||
ext.stMinSdkVersion = 26
|
||||
ext.stTargetSdkVersion = 33
|
||||
ext.startupVersion = "1.1.1"
|
||||
ext.workVersion = "2.7.1"
|
||||
ext.coreKtxVersion = "1.9.0"
|
||||
ext.testKtxVersion = "1.5.0"
|
||||
ext.materialVersion = "1.8.0"
|
||||
ext.androidxTestExtJunitVersion = "1.1.5"
|
||||
ext.androidxTestEspressoCoreVersion = "3.5.1"
|
||||
ext.androidxAnnotationVersion = "1.5.0"
|
||||
ext.preferenceVersion = "1.2.0"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
@ -44,12 +43,13 @@ buildscript {
|
|||
// room のバージョンの影響で google-services を上げられない場合がある
|
||||
classpath "com.google.gms:google-services:4.3.15"
|
||||
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
//noinspection DifferentKotlinGradleVersion
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
|
||||
|
||||
classpath "com.github.bjoernq:unmockplugin:0.7.6"
|
||||
|
||||
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version"
|
||||
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ apply plugin: "com.android.library"
|
|||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
targetSdkVersion stTargetSdkVersion
|
||||
minSdkVersion stMinSdkVersion
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
freeCompilerArgs += [
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
|
@ -37,6 +37,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||
implementation project(":base")
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ apply plugin: "com.android.library"
|
|||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -11,8 +11,8 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
targetSdkVersion stTargetSdkVersion
|
||||
minSdkVersion stMinSdkVersion
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
}
|
||||
namespace 'jp.juggler.emoji'
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace "jp.juggler.icon_material_symbols"
|
||||
compileSdk compile_sdk_version
|
||||
compileSdk stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
defaultConfig {
|
||||
minSdk min_sdk_version
|
||||
targetSdk target_sdk_version
|
||||
minSdk stMinSdkVersion
|
||||
targetSdk stTargetSdkVersion
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
@ -24,6 +25,6 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = kotlinJvmTarget
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ apply plugin: "com.android.application"
|
|||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
compileSdkVersion compile_sdk_version
|
||||
buildToolsVersion build_tools_version
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
buildToolsVersion stBuildToolsVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -11,8 +11,8 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
targetSdkVersion stTargetSdkVersion
|
||||
minSdkVersion stMinSdkVersion
|
||||
|
||||
applicationId "jp.juggler.apng.sample"
|
||||
versionCode 1
|
||||
|
@ -36,7 +36,7 @@ android {
|
|||
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = jvm_target
|
||||
jvmTarget = kotlinJvmTarget
|
||||
freeCompilerArgs += [
|
||||
"-opt-in=kotlin.ExperimentalStdlibApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
|
@ -49,7 +49,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_lib_bersion"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||
implementation project(":base")
|
||||
implementation project(":apng_android")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
include ':app', ':sample_apng', ':apng_android', ':apng', ':colorpicker', ':emoji'
|
||||
include ':icon_material_symbols'
|
||||
include ':base'
|
||||
include ':anko'
|
||||
|
|
Loading…
Reference in New Issue