diff --git a/app_fenrir/build.gradle b/app_fenrir/build.gradle index 6e2b49c1c..0ced149a0 100644 --- a/app_fenrir/build.gradle +++ b/app_fenrir/build.gradle @@ -36,7 +36,6 @@ android { buildConfigField("String", "SERVICE_TOKEN", asStringVar(vk_service_token)) buildConfigField("String", "FAKE_ABI", asStringVar(fake_abi)) buildConfigField("String", "FAKE_DEVICE", asStringVar(fake_device)) - buildConfigField("String", "FCM_SESSION_ID_GEN_URL", asStringVar(fcm_session_id_gen_url)) buildConfigField("boolean", "MANAGE_SCOPED_STORAGE", "true") buildConfigField("boolean", "FORCE_DEVELOPER_MODE", is_developer_build ? "true" : "false") @@ -128,7 +127,6 @@ dependencies { implementation("androidx.webkit:webkit:$webkitVersion") implementation("androidx.exifinterface:exifinterface:$exifinterfaceVersion") implementation("io.reactivex.rxjava3:rxjava:$rxJavaVersion") - implementation("com.google.firebase:firebase-database:$firebaseDatabaseVersion") implementation("com.google.firebase:firebase-datatransport:$firebaseDatatransportVersion") implementation("com.google.firebase:firebase-messaging:$firebaseMessagingVersion") { exclude group: "com.google.firebase", module: "firebase-installations" diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt index fffbfabb0..b195dae11 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt @@ -18,14 +18,13 @@ object Constants { @AccountType val DEFAULT_ACCOUNT_TYPE: Int = AccountType.toAccountType(BuildConfig.DEFAULT_ACCOUNT_TYPE) - const val FCM_SESSION_ID_GEN_URL: String = BuildConfig.FCM_SESSION_ID_GEN_URL val AUTH_VERSION = if (DEFAULT_ACCOUNT_TYPE == AccountType.KATE) API_VERSION else "5.122" const val FILE_PROVIDER_AUTHORITY: String = BuildConfig.APPLICATION_ID + ".file_provider" - const val VK_ANDROID_APP_VERSION_NAME = "8.8" - const val VK_ANDROID_APP_VERSION_CODE = "14796" - const val KATE_APP_VERSION_NAME = "95 lite" - const val KATE_APP_VERSION_CODE = "528" + const val VK_ANDROID_APP_VERSION_NAME = "8.11" + const val VK_ANDROID_APP_VERSION_CODE = "15026" + const val KATE_APP_VERSION_NAME = "96 lite" + const val KATE_APP_VERSION_CODE = "529" const val API_ID: Int = BuildConfig.VK_API_APP_ID const val SECRET: String = BuildConfig.VK_CLIENT_SECRET const val MAIN_OWNER_FIELDS = UserColumns.API_FIELDS + "," + GroupColumns.API_FIELDS diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/DeltaOwnerActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/DeltaOwnerActivity.kt index 949232867..3248467ae 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/DeltaOwnerActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/DeltaOwnerActivity.kt @@ -47,7 +47,6 @@ import dev.ragnarok.fenrir.util.toast.CustomToast import io.reactivex.rxjava3.disposables.Disposable import java.io.File import java.io.FileOutputStream -import java.nio.charset.StandardCharsets import java.text.DateFormat import java.text.SimpleDateFormat @@ -126,7 +125,7 @@ class DeltaOwnerActivity : AppCompatActivity(), PlaceProvider, AppStyleable { DeltaOwner.serializer(), delta ).toByteArray( - StandardCharsets.UTF_8 + Charsets.UTF_8 ) out = FileOutputStream(file) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt index 0fb1098c5..d546a4cf0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt @@ -147,6 +147,7 @@ import dev.ragnarok.fenrir.model.drawer.AbsMenuItem import dev.ragnarok.fenrir.model.drawer.RecentChat import dev.ragnarok.fenrir.model.drawer.SectionMenuItem import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.thorvg.ThorVGRender import dev.ragnarok.fenrir.place.Place import dev.ragnarok.fenrir.place.PlaceFactory import dev.ragnarok.fenrir.place.PlaceProvider @@ -322,6 +323,19 @@ open class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks, OnSect } setTheme(currentStyle()) Utils.prepareDensity(this) + + if (FenrirNative.isNativeLoaded && getMainActivityTransform() == MainActivityTransforms.MAIN) { + ThorVGRender.registerColors( + mapOf( + "primary_color" to CurrentTheme.getColorPrimary(this), + "secondary_color" to CurrentTheme.getColorSecondary(this), + "on_surface_color" to CurrentTheme.getColorOnSurface(this), + "white_color_contrast_fix" to CurrentTheme.getColorWhiteContrastFix(this), + "black_color_contrast_fix" to CurrentTheme.getColorBlackContrastFix(this) + ) + ) + } + super.onCreate(savedInstanceState) isActivityDestroyed = false isZoomPhoto = Settings.get().other().isDo_zoom_photo diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/AbsVkApiInterceptor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/AbsVkApiInterceptor.kt index 4b294772d..022a9879b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/AbsVkApiInterceptor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/AbsVkApiInterceptor.kt @@ -77,7 +77,7 @@ abstract class AbsVkApiInterceptor(private val version: String) : } return chain.proceed( original.newBuilder() - .method("POST", formBuilder.build()) + .post(formBuilder.build()) .build() ) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt index 200307d67..1ccdfba7d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/OtherVkRetrofitProvider.kt @@ -47,7 +47,10 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v override fun provideAuthRetrofit(): Single { return Single.fromCallable { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(30, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(false).vkHeader(true) @@ -75,7 +78,10 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v override fun provideAuthServiceRetrofit(): Single { return Single.fromCallable { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(30, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(false).vkHeader(true) @@ -103,7 +109,10 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v private fun createLocalServerRetrofit(): Retrofit { val localSettings = Settings.get().other().localServer val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(30, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(false).vkHeader(false).addHeader( @@ -125,7 +134,7 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v formBuilder.add("password", it) } val request = original.newBuilder() - .method("POST", formBuilder.build()) + .post(formBuilder.build()) .build() chain.proceed(request) }).addInterceptor(UncompressDefaultInterceptor) @@ -144,7 +153,10 @@ class OtherVkRetrofitProvider @SuppressLint("CheckResult") constructor(private v private fun createLongpollRetrofitInstance(): Retrofit { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(30, TimeUnit.SECONDS) + .readTimeout(25, TimeUnit.SECONDS) + .connectTimeout(25, TimeUnit.SECONDS) + .writeTimeout(25, TimeUnit.SECONDS) + .callTimeout(25, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(false).vkHeader(true).addHeader( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt index 85604d0e3..429884c43 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/UploadRetrofitProvider.kt @@ -48,9 +48,10 @@ class UploadRetrofitProvider(private val proxySettings: IProxySettings) : IUploa private fun createUploadRetrofit(): Retrofit { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(false).vkHeader(true).addHeader( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt index 4b42ad983..00f1c7b69 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/VkMethodHttpClientFactory.kt @@ -56,9 +56,10 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { ): OkHttpClient { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .addInterceptor(interceptor) - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(true).vkHeader(false) .addHeader("User-Agent", Constants.USER_AGENT(interceptor.type)).build() @@ -75,9 +76,10 @@ class VkMethodHttpClientFactory : IVkMethodHttpClientFactory { config: ProxyConfig? ): OkHttpClient { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> val request = chain.toRequestBuilder(true).vkHeader(false) .addHeader("User-Agent", Constants.USER_AGENT(type)).build() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt index f22c946ce..adfce6b27 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AbsApi.kt @@ -14,6 +14,7 @@ import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.refresh.RefreshToken import dev.ragnarok.fenrir.util.serializeble.json.decodeFromStream import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack +import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import dev.ragnarok.fenrir.util.serializeble.retrofit.kotlinx.serialization.Serializer import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single @@ -21,7 +22,6 @@ import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.exceptions.Exceptions import io.reactivex.rxjava3.functions.Function import okhttp3.* -import java.io.IOException import java.lang.reflect.Type import java.util.* @@ -51,19 +51,21 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe .url( method ) - .method("POST", bodyBuilder.build()) + .post(bodyBuilder.build()) .build() val call = client.newCall(request) emitter.setCancellable { call.cancel() } - call.enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - emitter.onError(e) - } - - override fun onResponse(call: Call, response: Response) { + try { + val response = call.execute() + if (!response.isSuccessful) { + emitter.onError(HttpCodeException(response.code)) + } else { emitter.onSuccess(response) } - }) + response.close() + } catch (e: Exception) { + emitter.onError(e) + } } } .map { response -> @@ -112,19 +114,21 @@ internal open class AbsApi(val accountId: Int, private val retrofitProvider: ISe .url( method ) - .method("POST", bodyBuilder.build()) + .post(bodyBuilder.build()) .build() val call = client.newCall(request) emitter.setCancellable { call.cancel() } - call.enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - emitter.onError(e) - } - - override fun onResponse(call: Call, response: Response) { + try { + val response = call.execute() + if (!response.isSuccessful) { + emitter.onError(HttpCodeException(response.code)) + } else { emitter.onSuccess(response) } - }) + response.close() + } catch (e: Exception) { + emitter.onError(e) + } } } .map { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt index 339471121..940bf3472 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/OtherApi.kt @@ -5,10 +5,13 @@ import dev.ragnarok.fenrir.api.interfaces.IOtherApi import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.Optional import dev.ragnarok.fenrir.util.Optional.Companion.wrap +import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleEmitter -import okhttp3.* -import java.io.IOException +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody class OtherApi(private val accountId: Int, private val provider: IVkRetrofitProvider) : IOtherApi { override fun rawRequest( @@ -28,19 +31,21 @@ class OtherApi(private val accountId: Int, private val provider: IVkRetrofitProv "https://" + Settings.get() .other().get_Api_Domain() + "/method/" + method ) - .method("POST", bodyBuilder.build()) + .post(bodyBuilder.build()) .build() val call = client.newCall(request) emitter.setCancellable { call.cancel() } - call.enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - emitter.onError(e) - } - - override fun onResponse(call: Call, response: Response) { + try { + val response = call.execute() + if (!response.isSuccessful) { + emitter.onError(HttpCodeException(response.code)) + } else { emitter.onSuccess(response) } - }) + response.close() + } catch (e: Exception) { + emitter.onError(e) + } } } .map { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt index 4c9bf1203..2172fe948 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/UtilsApi.kt @@ -97,4 +97,17 @@ internal class UtilsApi(accountId: Int, provider: IServiceProvider) : .map(extractResponseWithErrorHandling()) } } + + override fun getServerTime(): Single { + return provideService( + IUtilsService::class.java, + TokenType.USER, + TokenType.COMMUNITY, + TokenType.SERVICE + ) + .flatMap { service -> + service.getServerTime() + .map(extractResponseWithErrorHandling()) + } + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUtilsApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUtilsApi.kt index a64cb82ba..f29bc3f60 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUtilsApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IUtilsApi.kt @@ -33,4 +33,7 @@ interface IUtilsApi { @CheckResult fun customScript(code: String?): Single + + @CheckResult + fun getServerTime(): Single } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt index 5adaee73c..32eb5bc3d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IUtilsService.kt @@ -10,6 +10,7 @@ import dev.ragnarok.fenrir.api.model.response.VKApiLinkResponse import io.reactivex.rxjava3.core.Single import retrofit2.http.Field import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET import retrofit2.http.POST interface IUtilsService { @@ -53,4 +54,8 @@ interface IUtilsService { @FormUrlEncoded @POST("execute") fun customScript(@Field("code") code: String?): Single> + + @FormUrlEncoded + @GET("utils.getServerTime") + fun getServerTime(): Single> } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/AESCrypt.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/AESCrypt.kt index 8161a7cb8..8f840d743 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/AESCrypt.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/AESCrypt.kt @@ -3,7 +3,6 @@ package dev.ragnarok.fenrir.crypt import android.util.Base64 import android.util.Log import java.io.UnsupportedEncodingException -import java.nio.charset.StandardCharsets import java.security.GeneralSecurityException import java.security.MessageDigest import java.security.NoSuchAlgorithmException @@ -53,7 +52,7 @@ object AESCrypt { @Throws(NoSuchAlgorithmException::class) private fun generateKey(password: String): SecretKeySpec { val digest = MessageDigest.getInstance(HASH_ALGORITHM) - val bytes = password.toByteArray(StandardCharsets.UTF_8) + val bytes = password.toByteArray(Charsets.UTF_8) digest.update(bytes, 0, bytes.size) val key = digest.digest() log("SHA-256 key ", key) @@ -73,7 +72,7 @@ object AESCrypt { return try { val key = generateKey(password) log("message", message) - val cipherText = encrypt(key, ivBytes, message.toByteArray(StandardCharsets.UTF_8)) + val cipherText = encrypt(key, ivBytes, message.toByteArray(Charsets.UTF_8)) //NO_WRAP is important as was getting \n at the end val encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP) @@ -121,7 +120,7 @@ object AESCrypt { log("decodedCipherText", decodedCipherText) val decryptedBytes = decrypt(key, ivBytes, decodedCipherText) log("decryptedBytes", decryptedBytes) - val message = String(decryptedBytes, StandardCharsets.UTF_8) + val message = String(decryptedBytes, Charsets.UTF_8) log("message", message) message } catch (e: UnsupportedEncodingException) { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/FirebaseSessionIdGenerator.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/FirebaseSessionIdGenerator.kt deleted file mode 100644 index 88bce5dbf..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/FirebaseSessionIdGenerator.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.ragnarok.fenrir.crypt - -import com.google.firebase.database.* -import dev.ragnarok.fenrir.Constants -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleEmitter - -class FirebaseSessionIdGenerator : ISessionIdGenerator { - override fun generateNextId(): Single { - val database = FirebaseDatabase.getInstance(Constants.FCM_SESSION_ID_GEN_URL) - val ref = database.reference - val databaseCounter = ref.child("key_exchange_session_counter") - - //https://stackoverflow.com/questions/28915706/auto-increment-a-value-in-firebase - return Single.create { emitter: SingleEmitter -> - databaseCounter.runTransaction(object : Transaction.Handler { - var nextValue: Long = 0 - override fun doTransaction(currentData: MutableData): Transaction.Result { - nextValue = if (currentData.value == null) { - 1 - } else { - ((currentData.value as Long?) ?: 0) + 1 - } - currentData.value = nextValue - return Transaction.success(currentData) - } - - override fun onComplete( - e: DatabaseError?, - committed: Boolean, - currentData: DataSnapshot? - ) { - if (e != null) { - emitter.onError(SessionIdGenerationException(e.message)) - } else { - emitter.onSuccess(nextValue) - } - } - }) - } - } -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/ISessionIdGenerator.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/ISessionIdGenerator.kt deleted file mode 100644 index 3981af118..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/ISessionIdGenerator.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.ragnarok.fenrir.crypt - -import io.reactivex.rxjava3.core.Single - -interface ISessionIdGenerator { - fun generateNextId(): Single -} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/KeyExchangeService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/KeyExchangeService.kt index 3c9dba67a..14a07ef29 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/KeyExchangeService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/crypt/KeyExchangeService.kt @@ -25,6 +25,8 @@ import dev.ragnarok.fenrir.crypt.CryptHelper.generateRsaKeyPair import dev.ragnarok.fenrir.crypt.ver.Version.currentVersion import dev.ragnarok.fenrir.crypt.ver.Version.ofCurrent import dev.ragnarok.fenrir.db.Stores +import dev.ragnarok.fenrir.domain.IUtilsInteractor +import dev.ragnarok.fenrir.domain.InteractorFactory import dev.ragnarok.fenrir.longpoll.AppNotificationChannels import dev.ragnarok.fenrir.model.Peer import dev.ragnarok.fenrir.push.OwnerInfo @@ -47,7 +49,7 @@ import javax.crypto.IllegalBlockSizeException import javax.crypto.NoSuchPaddingException class KeyExchangeService : Service() { - private val mSessionIdGenerator: ISessionIdGenerator = FirebaseSessionIdGenerator() + private val mUtilsInteractor: IUtilsInteractor = InteractorFactory.createUtilsInteractor() private val mCompositeSubscription = CompositeDisposable() private var mCurrentActiveSessions: LongSparseArray = LongSparseArray(1) private var mCurrentActiveNotifications: LongSparseArray = @@ -88,7 +90,7 @@ class KeyExchangeService : Service() { @KeyLocationPolicy val keyLocationPolicy = intent.extras!!.getInt( EXTRA_KEY_LOCATION_POLICY ) - iniciateKeyExchange(accountId, peerId, keyLocationPolicy) + initiateKeyExchange(accountId, peerId, keyLocationPolicy) } ACTION_APPLY_EXHANGE -> { val accountId = intent.extras!!.getInt(Extra.ACCOUNT_ID) @@ -165,7 +167,7 @@ class KeyExchangeService : Service() { } @SuppressLint("CheckResult") - private fun iniciateKeyExchange( + private fun initiateKeyExchange( accountId: Int, peerId: Int, @KeyLocationPolicy keyLocationPolicy: Int @@ -176,7 +178,7 @@ class KeyExchangeService : Service() { .showToastInfo(R.string.session_already_created) return } - mSessionIdGenerator.generateNextId() + mUtilsInteractor.getServerTime(accountId) .fromIOToMain() .subscribe({ val session = KeyExchangeSession.createOutSession( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IUtilsInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IUtilsInteractor.kt index 16b0cb339..73cd923bf 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IUtilsInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IUtilsInteractor.kt @@ -24,4 +24,5 @@ interface IUtilsInteractor { fun joinChatByInviteLink(accountId: Int, link: String?): Single fun getInviteLink(accountId: Int, peer_id: Int?, reset: Int?): Single fun customScript(accountId: Int, code: String?): Single + fun getServerTime(accountId: Int): Single } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/AudioInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/AudioInteractor.kt index e332fc285..17546fc09 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/AudioInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/AudioInteractor.kt @@ -274,7 +274,6 @@ class AudioInteractor(private val networker: INetworker) : IAudioInteractor { return networker.vkDefault(accountId) .audio() .removeFromPlaylist(ownerId, playlist_id, audio_ids) - .map { resultId -> resultId } } override fun addToPlaylist( @@ -357,7 +356,6 @@ class AudioInteractor(private val networker: INetworker) : IAudioInteractor { return networker.vkDefault(accountId) .audio() .deletePlaylist(playlist_id, ownerId) - .map { resultId -> resultId } } override fun search( @@ -405,7 +403,6 @@ class AudioInteractor(private val networker: INetworker) : IAudioInteractor { items.items ) } - .map { out -> out } } override fun searchPlaylists( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/FeedInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/FeedInteractor.kt index e925b6f15..eef2833fb 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/FeedInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/FeedInteractor.kt @@ -249,17 +249,17 @@ class FeedInteractor( override fun saveList(accountId: Int, title: String?, listIds: Collection): Single { return networker.vkDefault(accountId) - .newsfeed().saveList(title, listIds).map { response -> response } + .newsfeed().saveList(title, listIds) } override fun addBan(accountId: Int, listIds: Collection): Single { return networker.vkDefault(accountId) - .newsfeed().addBan(listIds).map { response -> response } + .newsfeed().addBan(listIds) } override fun deleteBan(accountId: Int, listIds: Collection): Single { return networker.vkDefault(accountId) - .newsfeed().deleteBan(listIds).map { response -> response } + .newsfeed().deleteBan(listIds) } override fun getBanned(accountId: Int): Single> { @@ -271,7 +271,7 @@ class FeedInteractor( override fun deleteList(accountId: Int, list_id: Int?): Single { return networker.vkDefault(accountId) - .newsfeed().deleteList(list_id).map { response -> response } + .newsfeed().deleteList(list_id) } override fun ignoreItem( @@ -281,7 +281,7 @@ class FeedInteractor( item_id: Int? ): Single { return networker.vkDefault(accountId) - .newsfeed().ignoreItem(type, owner_id, item_id).map { response -> response } + .newsfeed().ignoreItem(type, owner_id, item_id) } override fun getCachedFeedLists(accountId: Int): Single> { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/PhotosInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/PhotosInteractor.kt index fae7c9152..c7043b65a 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/PhotosInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/PhotosInteractor.kt @@ -218,7 +218,6 @@ class PhotosInteractor(private val networker: INetworker, private val cache: ISt ): Single> { return networker.vkDefault(accountId) .photos().getTags(ownerId, photo_id, access_key) - .map { items -> items } } override fun getAllComments( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/UtilsInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/UtilsInteractor.kt index c2006c27e..e0961a259 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/UtilsInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/UtilsInteractor.kt @@ -210,21 +210,18 @@ class UtilsInteractor( return networker.vkDefault(accountId) .utils() .deleteFromLastShortened(key) - .map { out -> out } } override fun checkLink(accountId: Int, url: String?): Single { return networker.vkDefault(accountId) .utils() .checkLink(url) - .map { out -> out } } override fun joinChatByInviteLink(accountId: Int, link: String?): Single { return networker.vkDefault(accountId) .utils() .joinChatByInviteLink(link) - .map { out -> out } } override fun getInviteLink( @@ -235,13 +232,17 @@ class UtilsInteractor( return networker.vkDefault(accountId) .utils() .getInviteLink(peer_id, reset) - .map { out -> out } } override fun customScript(accountId: Int, code: String?): Single { return networker.vkDefault(accountId) .utils() .customScript(code) - .map { out -> out } + } + + override fun getServerTime(accountId: Int): Single { + return networker.vkDefault(accountId) + .utils() + .getServerTime() } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt index 55affc66f..aabe9fc5b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt @@ -1936,15 +1936,16 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree requireActivity() ) if (FenrirNative.isNativeLoaded) { + anim.visibility = View.VISIBLE anim.fromRes( cbc.lottieRes, - Utils.dp(cbc.widthHeight), - Utils.dp(cbc.widthHeight), - cbc.replacement + Utils.dp(cbc.lottie_widthHeight), + Utils.dp(cbc.lottie_widthHeight), + cbc.lottie_replacement ) anim.playAnimation() } else { - anim.setImageResource(cbc.iconRes) + anim.visibility = View.GONE } MaterialAlertDialogBuilder(requireActivity()) .setView(view) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/abswall/AbsWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/abswall/AbsWallFragment.kt index 16cd9b587..40e756f2e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/abswall/AbsWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/abswall/AbsWallFragment.kt @@ -38,6 +38,7 @@ import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.Option import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.OptionRequest import dev.ragnarok.fenrir.model.* import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.thorvg.ThorVGRender import dev.ragnarok.fenrir.place.PlaceFactory.getAudiosPlace import dev.ragnarok.fenrir.place.PlaceFactory.getNarrativesPlace import dev.ragnarok.fenrir.place.PlaceFactory.getOwnerArticles @@ -49,12 +50,9 @@ import dev.ragnarok.fenrir.place.PlaceFactory.getWallAttachmentsPlace import dev.ragnarok.fenrir.place.PlaceUtil.goToPostCreation import dev.ragnarok.fenrir.place.PlaceUtil.goToPostEditor import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.AppPerms +import dev.ragnarok.fenrir.util.* import dev.ragnarok.fenrir.util.AppPerms.requestPermissionsAbs import dev.ragnarok.fenrir.util.AppTextUtils.getCounterWithK -import dev.ragnarok.fenrir.util.FindAttachmentType -import dev.ragnarok.fenrir.util.HelperSimple -import dev.ragnarok.fenrir.util.InputWallOffsetDialog import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.is600dp import dev.ragnarok.fenrir.util.Utils.isLandscape @@ -78,22 +76,32 @@ abstract class AbsWallFragment> : Runes?.visibility = if (Settings.get() .other().isRunes_show ) View.VISIBLE else View.GONE + if (!FenrirNative.isNativeLoaded) { + paganSymbol?.visibility = View.GONE + return + } val symbol = Settings.get().other().paganSymbol paganSymbol?.visibility = if (symbol != 0) View.VISIBLE else View.GONE if (symbol == 0) { return } val pic = Common.requirePaganSymbol(symbol, requireActivity()) - if (pic.isAnimation && FenrirNative.isNativeLoaded) { + if (pic.isAnimation) { paganSymbol?.fromRes( pic.lottieRes, - dp(pic.widthHeight), - dp(pic.widthHeight), - pic.replacement, pic.useMoveColor + dp(pic.lottie_widthHeight), + dp(pic.lottie_widthHeight), + pic.lottie_replacement, pic.lottie_useMoveColor ) paganSymbol?.playAnimation() } else { - paganSymbol?.setImageResource(pic.iconRes) + paganSymbol?.setImageBitmap( + ThorVGRender.createBitmap( + pic.iconRes, + dp(pic.icon_width), + dp(pic.icon_height) + ) + ) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt index 398a2058d..eff4674a4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt @@ -70,6 +70,7 @@ import dev.ragnarok.fenrir.util.ViewUtils.setupSwipeRefreshLayoutWithCurrentThem import dev.ragnarok.fenrir.util.rxutils.RxUtils import dev.ragnarok.fenrir.util.serializeble.json.* import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack +import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import io.reactivex.rxjava3.core.Single @@ -77,12 +78,9 @@ import io.reactivex.rxjava3.core.SingleEmitter import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.exceptions.Exceptions import okhttp3.* -import okhttp3.Call import java.io.File import java.io.FileInputStream import java.io.FileOutputStream -import java.io.IOException -import java.nio.charset.StandardCharsets import java.util.* class AccountsFragment : BaseFragment(), View.OnClickListener, AccountAdapter.Callback, @@ -284,7 +282,7 @@ class AccountsFragment : BaseFragment(), View.OnClickListener, AccountAdapter.Ca val settings = SettingsBackup().doBackup() root.put("settings", settings) val bytes = Json { prettyPrint = true }.printJsonElement(root.build()).toByteArray( - StandardCharsets.UTF_8 + Charsets.UTF_8 ) out = FileOutputStream(file) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) @@ -747,19 +745,21 @@ class AccountsFragment : BaseFragment(), View.OnClickListener, AccountAdapter.Ca "https://" + Settings.get().other() .get_Api_Domain() + "/method/users.get" ) - .method("POST", bodyBuilder.build()) + .post(bodyBuilder.build()) .build() val call = client.newCall(request) emitter.setCancellable { call.cancel() } - call.enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - emitter.onError(e) - } - - override fun onResponse(call: Call, response: Response) { + try { + val response = call.execute() + if (!response.isSuccessful) { + emitter.onError(HttpCodeException(response.code)) + } else { emitter.onSuccess(response) } - }) + response.close() + } catch (e: Exception) { + emitter.onError(e) + } } } .map>> { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListPresenter.kt index 117ce083a..358632553 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListPresenter.kt @@ -187,14 +187,17 @@ class CatalogV2ListPresenter( for (i in srt) { makeByUid(i, data.sections) } - view?.notifyDataSetChanged() var pos = 0 for (i in 0 until data.sections?.size.orZero()) { + if (query.nonNullNoEmpty() && data.sections?.get(i)?.title.isNullOrEmpty()) { + data.sections?.get(i)?.updateTitle(query) + } if (data.sections?.get(i)?.id == data.default_section) { pos = i break } } + view?.notifyDataSetChanged() if (srt[0] == TYPE_CATALOG) { view?.setSection(pos) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/friends/friendsbyphones/FriendsByPhonesPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/friends/friendsbyphones/FriendsByPhonesPresenter.kt index 796b8c263..251835f44 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/friends/friendsbyphones/FriendsByPhonesPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/friends/friendsbyphones/FriendsByPhonesPresenter.kt @@ -17,7 +17,6 @@ import dev.ragnarok.fenrir.util.serializeble.json.Json import kotlinx.serialization.builtins.ListSerializer import java.io.File import java.io.FileOutputStream -import java.nio.charset.StandardCharsets import java.util.* class FriendsByPhonesPresenter(accountId: Int, context: Context, savedInstanceState: Bundle?) : @@ -61,7 +60,7 @@ class FriendsByPhonesPresenter(accountId: Int, context: Context, savedInstanceSt val bytes = Json { prettyPrint = true }.encodeToString(ListSerializer(ContactConversation.serializer()), data).toByteArray( - StandardCharsets.UTF_8 + Charsets.UTF_8 ) out = FileOutputStream(file) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallFragment.kt index 9d2d6c290..0b27ea4a6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallFragment.kt @@ -243,7 +243,7 @@ class GroupWallFragment : AbsWallFragment(), } } - override fun InvalidateOptionsMenu() { + override fun invalidateOptionsMenu() { requireActivity().invalidateOptionsMenu() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallPresenter.kt index f4fbd0fb8..b506a9d38 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/GroupWallPresenter.kt @@ -44,7 +44,7 @@ class GroupWallPresenter( } private fun resolveMenu() { - view?.InvalidateOptionsMenu() + view?.invalidateOptionsMenu() } private fun resolveCounters() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/IGroupWallView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/IGroupWallView.kt index 95b14b03b..28375bf97 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/IGroupWallView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/groupwall/IGroupWallView.kt @@ -36,7 +36,7 @@ interface IGroupWallView : IWallView { narratives: Int ) - fun InvalidateOptionsMenu() + fun invalidateOptionsMenu() interface IOptionMenuView { fun setControlVisible(visible: Boolean) fun setIsSubscribed(subscribed: Boolean) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/requestexecute/RequestExecutePresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/requestexecute/RequestExecutePresenter.kt index 600b46dbc..846a2501f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/requestexecute/RequestExecutePresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/requestexecute/RequestExecutePresenter.kt @@ -28,7 +28,6 @@ import kotlinx.serialization.encodeToString import okhttp3.ResponseBody import java.io.File import java.io.FileOutputStream -import java.nio.charset.StandardCharsets import java.util.* class RequestExecutePresenter(accountId: Int, savedInstanceState: Bundle?) : @@ -93,7 +92,7 @@ class RequestExecutePresenter(accountId: Int, savedInstanceState: Bundle?) : val filename = makeLegalFilename(rMethod, "json") val file = File(Environment.getExternalStorageDirectory(), filename) file.delete() - val bytes = fullResponseBody?.toByteArray(StandardCharsets.UTF_8) ?: return + val bytes = fullResponseBody?.toByteArray(Charsets.UTF_8) ?: return out = FileOutputStream(file) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) out.write(bom) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt index f39b6858d..670fc3fe5 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt @@ -13,7 +13,7 @@ import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.settings.CurrentTheme.getColorPrimary import dev.ragnarok.fenrir.settings.CurrentTheme.getColorSecondary -import dev.ragnarok.fenrir.settings.CurrentTheme.getColorWhite +import dev.ragnarok.fenrir.settings.CurrentTheme.getColorWhiteContrastFix import dev.ragnarok.fenrir.settings.Settings.get import dev.ragnarok.fenrir.settings.theme.ThemeValue import dev.ragnarok.fenrir.util.Utils @@ -77,7 +77,7 @@ class ThemeAdapter(private var data: List, context: Context) : Utils.dp(120f), intArrayOf( 0x333333, - getColorWhite(holder.selected.context), + getColorWhiteContrastFix(holder.selected.context), 0x777777, getColorPrimary(holder.selected.context), 0x999999, @@ -115,7 +115,7 @@ class ThemeAdapter(private var data: List, context: Context) : Utils.dp(120f), intArrayOf( 0x333333, - getColorWhite(holder.selected.context), + getColorWhiteContrastFix(holder.selected.context), 0x777777, getColorPrimary(holder.selected.context), 0x999999, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/IUserWallView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/IUserWallView.kt index 9046db361..55d9a93f2 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/IUserWallView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/IUserWallView.kt @@ -37,9 +37,10 @@ interface IUserWallView : IWallView, IProgressView { ) fun displayUserStatus(statusText: String?, swAudioIcon: Boolean) - fun InvalidateOptionsMenu() + fun invalidateOptionsMenu() fun displayBaseUserInfo(user: User) fun openUserDetails(accountId: Int, user: User, details: UserDetails) fun showAvatarUploadedMessage(accountId: Int, post: Post) fun doEditPhoto(uri: Uri) + fun showRegistrationDate(date: String) } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallFragment.kt index 9b7a34ca2..f4e61e4df 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallFragment.kt @@ -348,6 +348,15 @@ class UserWallFragment : AbsWallFragment(), IU } } + override fun showRegistrationDate(date: String) { + MaterialAlertDialogBuilder(requireActivity()) + .setIcon(R.drawable.dir_person) + .setMessage(date) + .setTitle(getString(R.string.registration_date)) + .setCancelable(true) + .show() + } + override fun displayCounters( friends: Int, mutual: Int, @@ -559,7 +568,7 @@ class UserWallFragment : AbsWallFragment(), IU getMentionsPlace(accountId, ownerId).tryOpenWith(requireActivity()) } - override fun InvalidateOptionsMenu() { + override fun invalidateOptionsMenu() { requireActivity().invalidateOptionsMenu() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt index 8ac9834c4..4b0879736 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userwall/UserWallPresenter.kt @@ -9,6 +9,9 @@ import androidx.annotation.StringRes import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.ragnarok.fenrir.* import dev.ragnarok.fenrir.Includes.provideMainThreadScheduler +import dev.ragnarok.fenrir.api.HttpLoggerAndParser.toRequestBuilder +import dev.ragnarok.fenrir.api.HttpLoggerAndParser.vkHeader +import dev.ragnarok.fenrir.api.ProxyUtil import dev.ragnarok.fenrir.api.model.VKApiUser import dev.ragnarok.fenrir.domain.* import dev.ragnarok.fenrir.domain.Repository.owners @@ -23,11 +26,19 @@ import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.upload.* import dev.ragnarok.fenrir.util.Pair import dev.ragnarok.fenrir.util.ShortcutUtils.createWallShortcutRx +import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.Utils.getCauseIfRuntime -import dev.ragnarok.fenrir.util.Utils.getRegistrationDate import dev.ragnarok.fenrir.util.Utils.singletonArrayList import dev.ragnarok.fenrir.util.rxutils.RxUtils.ignore +import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException +import io.reactivex.rxjava3.core.Single +import okhttp3.* import java.io.File +import java.text.DateFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern class UserWallPresenter( accountId: Int, @@ -535,7 +546,7 @@ class UserWallPresenter( } private fun resolveMenu() { - view?.InvalidateOptionsMenu() + view?.invalidateOptionsMenu() } fun fireAddToFrindsClick(message: String?) { @@ -672,8 +683,112 @@ class UserWallPresenter( onUserInfoUpdated() } + private fun parseResponse(str: String, pattern: Pattern): String? { + val matcher = pattern.matcher(str) + return if (matcher.find()) { + matcher.group(1) + } else null + } + + private fun getRegistrationDate(owner_id: Int): Single { + return Single.create { emitter -> + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) + .addInterceptor(Interceptor { chain: Interceptor.Chain -> + val request = + chain.toRequestBuilder(false).vkHeader(true).addHeader( + "User-Agent", Constants.USER_AGENT( + AccountType.BY_TYPE + ) + ).build() + chain.proceed(request) + }) + ProxyUtil.applyProxyConfig(builder, Includes.proxySettings.activeProxy) + val request: Request = Request.Builder() + .url("https://vk.com/foaf.php?id=$owner_id").build() + + val call = builder.build().newCall(request) + emitter.setCancellable { call.cancel() } + try { + val response = call.execute() + if (!response.isSuccessful) { + emitter.onError(HttpCodeException(response.code)) + } else { + val resp = response.body.string() + val locale = Utils.appLocale + try { + var registered: String? = null + var auth: String? = null + var changes: String? = null + var tmp = + parseResponse( + resp, + Pattern.compile("ya:created dc:date=\"(.*?)\"") + ) + if (tmp.nonNullNoEmpty()) { + registered = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse( + tmp + )?.let { + DateFormat.getDateInstance(1).format( + it + ) + } + } + tmp = parseResponse( + resp, + Pattern.compile("ya:lastLoggedIn dc:date=\"(.*?)\"") + ) + if (tmp.nonNullNoEmpty()) { + auth = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse( + tmp + )?.let { + DateFormat.getDateInstance(1).format( + it + ) + } + } + tmp = parseResponse( + resp, + Pattern.compile("ya:modified dc:date=\"(.*?)\"") + ) + if (tmp.nonNullNoEmpty()) { + changes = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse( + tmp + )?.let { + DateFormat.getDateInstance(1).format( + it + ) + } + } + emitter.onSuccess( + context.getString( + R.string.registration_date_info, + registered, + auth, + changes + ) + ) + } catch (e: ParseException) { + emitter.onError(e) + } + } + response.close() + } catch (e: Exception) { + emitter.onError(e) + } + } + } + fun fireGetRegistrationDate() { - getRegistrationDate(context, ownerId) + appendDisposable( + getRegistrationDate(ownerId).fromIOToMain() + .subscribe({ + view?.showRegistrationDate(it) + }, { showError(it) }) + ) } fun fireReport() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/NotificationHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/NotificationHelper.kt index 8544e60ac..772398218 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/NotificationHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/longpoll/NotificationHelper.kt @@ -634,6 +634,7 @@ object NotificationHelper { //MimeType = response.header("Content-Type", "image/jpeg"); output.flush() input.close() + response.close() } urit = FileProvider.getUriForFile(mContext, Constants.FILE_PROVIDER_AUTHORITY, file) mContext.grantUriPermission( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2List.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2List.kt index 2e7aa55ba..3190942b3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2List.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2List.kt @@ -63,6 +63,10 @@ class CatalogV2List : Parcelable { url = object_api.url } + fun updateTitle(title: String?) { + this.title = title + } + override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(id) parcel.writeString(title) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2Section.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2Section.kt index 2a9717819..56cf56f71 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2Section.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/catalog_v2_audio/CatalogV2Section.kt @@ -114,6 +114,7 @@ class CatalogV2Section : Parcelable { listContentType = s.data_type } } else { + listContentType = null op.add(s) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ChatDownloadWorker.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ChatDownloadWorker.kt index 2403b6a6b..7e30190b0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ChatDownloadWorker.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ChatDownloadWorker.kt @@ -42,7 +42,6 @@ import java.io.File import java.io.FileOutputStream import java.io.OutputStream import java.io.OutputStreamWriter -import java.nio.charset.StandardCharsets import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -68,7 +67,7 @@ class ChatDownloadWorker(context: Context, workerParams: WorkerParameters) : } private fun readBase64(`val`: String): String { - return String(Base64.decode(`val`, Base64.DEFAULT), StandardCharsets.UTF_8) + return String(Base64.decode(`val`, Base64.DEFAULT), Charsets.UTF_8) } private fun getAvatarUrl(owner: Owner?, owner_id: Int): String { @@ -481,7 +480,7 @@ class ChatDownloadWorker(context: Context, workerParams: WorkerParameters) : val output: OutputStream = FileOutputStream(html) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) output.write(bom) - output.write(main.toByteArray(StandardCharsets.UTF_8)) + output.write(main.toByteArray(Charsets.UTF_8)) output.flush() output.close() applicationContext.sendBroadcast( @@ -590,7 +589,7 @@ class ChatDownloadWorker(context: Context, workerParams: WorkerParameters) : ), "json" ) ) - val output = OutputStreamWriter(FileOutputStream(html), StandardCharsets.UTF_8) + val output = OutputStreamWriter(FileOutputStream(html), Charsets.UTF_8) val bom = charArrayOf('\ufeff') output.write(bom) var offset = 0 diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt index 8ea235d92..880caac18 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/ErrorLocalizer.kt @@ -5,6 +5,7 @@ import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.api.ApiException import dev.ragnarok.fenrir.exception.NotFoundException import dev.ragnarok.fenrir.nonNullNoEmpty +import dev.ragnarok.fenrir.util.serializeble.retrofit.HttpCodeException import retrofit2.HttpException import java.net.SocketTimeoutException import java.net.UnknownHostException @@ -31,6 +32,9 @@ object ErrorLocalizer { is HttpException -> { context.getString(R.string.vk_servers_error, throwable.code()) } + is HttpCodeException -> { + context.getString(R.string.vk_servers_error, throwable.code) + } else -> throwable.message.nonNullNoEmpty({ it }, { throwable.toString() }) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/FaveSyncWorker.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/FaveSyncWorker.kt index d75e74f80..96356ed47 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/FaveSyncWorker.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/service/FaveSyncWorker.kt @@ -31,7 +31,6 @@ import dev.ragnarok.fenrir.util.Utils.hasOreo import dev.ragnarok.fenrir.util.toast.CustomToast import java.io.File import java.io.FileOutputStream -import java.nio.charset.StandardCharsets import java.util.* import java.util.regex.Pattern import kotlin.math.abs @@ -394,6 +393,7 @@ class FaveSyncWorker(context: Context, workerParams: WorkerParameters) : } } + @SuppressLint("MissingPermission") private fun show_notification( notification: NotificationCompat.Builder, id: Int, @@ -560,7 +560,7 @@ class FaveSyncWorker(context: Context, workerParams: WorkerParameters) : }) try { val file = File(Environment.getExternalStorageDirectory(), "fenrir_fave_sync_log.txt") - FileOutputStream(file).write(log.toString().toByteArray(StandardCharsets.UTF_8)) + FileOutputStream(file).write(log.toString().toByteArray(Charsets.UTF_8)) applicationContext.sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/settings/CurrentTheme.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/settings/CurrentTheme.kt index 50d62c598..a720e3754 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/settings/CurrentTheme.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/settings/CurrentTheme.kt @@ -146,8 +146,20 @@ object CurrentTheme { return getColorFromAttrs(R.attr.toast_background, context, "#FFFFFF") } - fun getColorWhite(context: Context): Int { - return getColorFromAttrs(dev.ragnarok.fenrir_common.R.attr.white_color, context, "#FFFFFF") + fun getColorWhiteContrastFix(context: Context): Int { + return getColorFromAttrs( + dev.ragnarok.fenrir_common.R.attr.white_color_contrast_fix, + context, + "#FFFFFF" + ) + } + + fun getColorBlackContrastFix(context: Context): Int { + return getColorFromAttrs( + dev.ragnarok.fenrir_common.R.attr.black_color_contrast_fix, + context, + "#000000" + ) } private fun getColorHex(@ColorInt color: Int): String { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt index 436806bbf..d12c68a59 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioToMessageUploadable.kt @@ -39,7 +39,7 @@ class AudioToMessageUploadable( networker.vkDefault(accountId) .audio() .uploadServer - .map { s -> s } + .map { it } } else { Single.just(initialServer) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt index 8332be2eb..4dba617e6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/AudioUploadable.kt @@ -30,7 +30,7 @@ class AudioUploadable(private val context: Context, private val networker: INetw networker.vkDefault(accountId) .audio() .uploadServer - .map { s -> s } + .map { it } } else { Single.just(initialServer) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt index 8e744742c..89e7ae2dd 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/ChatPhotoUploadable.kt @@ -29,7 +29,7 @@ class ChatPhotoUploadable(private val context: Context, private val networker: I networker.vkDefault(accountId) .photos() .getChatUploadServer(chat_id) - .map { s -> s } + .map { it } } else { Single.just(initialServer) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt index de63e80dd..de20eb45d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/DocumentUploadable.kt @@ -41,7 +41,7 @@ class DocumentUploadable( networker.vkDefault(accountId) .docs() .getUploadServer(groupId) - .map { s -> s } + .map { it } } else { Single.just(initialServer) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt index 030ccc8fd..dfdc0ed76 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2MessageUploadable.kt @@ -40,7 +40,7 @@ class Photo2MessageUploadable( } else { networker.vkDefault(accountId) .photos() - .messagesUploadServer.map { s -> s } + .messagesUploadServer.map { it } } return serverSingle.flatMap { server -> var `is`: InputStream? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt index 2bb8fad4d..51153ff89 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/upload/impl/Photo2WallUploadable.kt @@ -39,7 +39,7 @@ class Photo2WallUploadable( networker.vkDefault(accountId) .photos() .getWallUploadServer(groupId) - .map { s -> s } + .map { it } } return serverSingle.flatMap { server -> var `is`: InputStream? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/DownloadWorkUtils.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/DownloadWorkUtils.kt index 11bca376a..dc81f5769 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/DownloadWorkUtils.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/DownloadWorkUtils.kt @@ -646,7 +646,7 @@ object DownloadWorkUtils { try { FileOutputStream(file).use { output -> if (url.isNullOrEmpty()) throw Exception(applicationContext.getString(R.string.null_image_link)) - val builder = Utils.createOkHttp(60, false) + val builder = Utils.createOkHttp(180, false) val request: Request = Request.Builder() .url(url) .build() @@ -680,6 +680,7 @@ object DownloadWorkUtils { NotificationHelper.NOTIFICATION_DOWNLOADING ) } + response.close() return false } output.write(data, 0, bufferLength) @@ -698,6 +699,7 @@ object DownloadWorkUtils { output.flush() output.close() input.close() + response.close() if (UseMediaScanner) { applicationContext.sendBroadcast( Intent( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt index 7b845f9a5..c0a369ef2 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Mp3InfoHelper.kt @@ -6,10 +6,9 @@ import io.reactivex.rxjava3.core.Single import okhttp3.Request object Mp3InfoHelper { - fun getLength(url: String): Single { return Single.create { - val builder = Utils.createOkHttp(60, false) + val builder = Utils.createOkHttp(15, false) val request: Request = Request.Builder() .url(url) .build() @@ -24,6 +23,7 @@ object Mp3InfoHelper { } else { val length = response.header("Content-Length") response.body.close() + response.close() if (length.isNullOrEmpty()) { it.onError(Exception("Empty content length!")) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/OkHttp3LoggingInterceptor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/OkHttp3LoggingInterceptor.kt index df1516979..439c1b5c7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/OkHttp3LoggingInterceptor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/OkHttp3LoggingInterceptor.kt @@ -14,7 +14,6 @@ import okio.Buffer import okio.GzipSource import okio.source import java.io.IOException -import java.nio.charset.Charset import java.util.* import java.util.concurrent.TimeUnit @@ -195,7 +194,7 @@ class OkHttp3LoggingInterceptor @JvmOverloads constructor( } } - val charset: Charset = requestBody.contentType().charset() + val charset = requestBody.contentType().charset() logger.log("") if (gzippedLength != null) { @@ -236,6 +235,8 @@ class OkHttp3LoggingInterceptor @JvmOverloads constructor( logger.log("<-- END HTTP") } else if (bodyHasUnknownEncoding(response.headers)) { logger.log("<-- END HTTP (encoded body omitted)") + } else if (bodyIsStreaming(response)) { + logger.log("<-- END HTTP (streaming)") } else { val source = responseBody.source() source.request(Long.MAX_VALUE) // Buffer the entire body. @@ -285,6 +286,11 @@ class OkHttp3LoggingInterceptor @JvmOverloads constructor( return response } + private fun bodyIsStreaming(response: Response): Boolean { + val contentType = response.body.contentType() + return contentType != null && contentType.type == "text" && contentType.subtype == "event-stream" + } + private fun logHeader(headers: Headers, i: Int) { val value = if (headers.name(i) in headersToRedact) "██" else headers.value(i) logger.log(headers.name(i) + ": " + value) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/PostDownload.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/PostDownload.kt index b80e865c9..444eefb60 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/PostDownload.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/PostDownload.kt @@ -25,7 +25,6 @@ import io.reactivex.rxjava3.core.Completable import java.io.File import java.io.FileOutputStream import java.io.OutputStream -import java.nio.charset.StandardCharsets import kotlin.math.abs class PostDownload(private val context: Context) { @@ -45,7 +44,7 @@ class PostDownload(private val context: Context) { } private fun readBase64(`val`: String): String { - return String(Base64.decode(`val`, Base64.DEFAULT), StandardCharsets.UTF_8) + return String(Base64.decode(`val`, Base64.DEFAULT), Charsets.UTF_8) } private fun getAvatarUrl(owner: Owner): String { @@ -427,7 +426,7 @@ class PostDownload(private val context: Context) { val output: OutputStream = FileOutputStream(html) val bom = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) output.write(bom) - output.write(main.toByteArray(StandardCharsets.UTF_8)) + output.write(main.toByteArray(Charsets.UTF_8)) output.flush() output.close() context.sendBroadcast( diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Utils.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Utils.kt index 3d6c516e8..359c74351 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Utils.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/Utils.kt @@ -14,7 +14,6 @@ import android.database.Cursor import android.graphics.* import android.graphics.drawable.Drawable import android.os.Build -import android.os.Handler import android.os.Parcel import android.util.SparseArray import android.util.TypedValue @@ -30,7 +29,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.google.android.exoplayer2.MediaItem -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import dev.ragnarok.fenrir.* import dev.ragnarok.fenrir.Constants.USER_AGENT @@ -63,15 +61,10 @@ import dev.ragnarok.fenrir.view.pager.* import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.Disposable import okhttp3.* -import okhttp3.Call import java.io.Closeable import java.io.IOException -import java.text.DateFormat -import java.text.ParseException -import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.TimeUnit -import java.util.regex.Pattern import kotlin.math.abs import kotlin.math.ceil import kotlin.math.floor @@ -1064,9 +1057,10 @@ object Utils { proxyConfig: ProxyConfig? ): OkHttpDataSource.Factory { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) applyProxyConfig(builder, proxyConfig) return OkHttpDataSource.Factory(builder.build()).setUserAgent(userAgent) } @@ -1279,12 +1273,12 @@ object Utils { .subscribe { function.call() } } - fun createOkHttp(timeouts: Int, compressIntercept: Boolean): OkHttpClient.Builder { val builder: OkHttpClient.Builder = OkHttpClient.Builder() .connectTimeout(timeouts.toLong(), TimeUnit.SECONDS) .readTimeout(timeouts.toLong(), TimeUnit.SECONDS) .writeTimeout(timeouts.toLong(), TimeUnit.SECONDS) + .callTimeout(timeouts.toLong(), TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> chain.proceed( chain.toRequestBuilder(false).vkHeader(true).addHeader( @@ -1484,95 +1478,6 @@ object Utils { } else info } - - internal fun parseResponse(str: String, pattern: Pattern): String? { - val matcher = pattern.matcher(str) - return if (matcher.find()) { - matcher.group(1) - } else null - } - - - fun getRegistrationDate(context: Context, owner_id: Int) { - val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(Interceptor { chain: Interceptor.Chain -> - val request = - chain.toRequestBuilder(false).vkHeader(true).addHeader( - "User-Agent", USER_AGENT( - AccountType.BY_TYPE - ) - ).build() - chain.proceed(request) - }) - applyProxyConfig(builder, proxySettings.activeProxy) - val request: Request = Request.Builder() - .url("https://vk.com/foaf.php?id=$owner_id").build() - builder.build().newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - e.printStackTrace() - } - - @Throws(IOException::class) - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val resp = response.body.string() - var result = context.getString(R.string.error) - val locale = appLocale - try { - var registered: String? = null - var auth: String? = null - var changes: String? = null - var tmp = - parseResponse(resp, Pattern.compile("ya:created dc:date=\"(.*?)\"")) - if (tmp.nonNullNoEmpty()) { - registered = DateFormat.getDateInstance(1).format( - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse(tmp) - ?: return - ) - } - tmp = parseResponse( - resp, - Pattern.compile("ya:lastLoggedIn dc:date=\"(.*?)\"") - ) - if (tmp.nonNullNoEmpty()) { - auth = DateFormat.getDateInstance(1).format( - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse(tmp) - ?: return - ) - } - tmp = parseResponse(resp, Pattern.compile("ya:modified dc:date=\"(.*?)\"")) - if (tmp.nonNullNoEmpty()) { - changes = DateFormat.getDateInstance(1).format( - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ", locale).parse(tmp) - ?: return - ) - } - result = context.getString( - R.string.registration_date_info, - registered, - auth, - changes - ) - } catch (e: ParseException) { - e.printStackTrace() - } - val uiHandler = Handler(context.mainLooper) - val finalResult = result - uiHandler.post { - MaterialAlertDialogBuilder(context) - .setIcon(R.drawable.dir_person) - .setMessage(finalResult) - .setTitle(context.getString(R.string.registration_date)) - .setCancelable(true) - .show() - } - } - } - }) - } - - fun createPageTransform(@Transformers_Types type: Int): ViewPager2.PageTransformer? { when (type) { Transformers_Types.SLIDER_TRANSFORMER -> return SliderTransformer(1) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/hls/M3U8.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/hls/M3U8.kt index eda7ffd89..f154a5f22 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/hls/M3U8.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/hls/M3U8.kt @@ -41,7 +41,7 @@ class M3U8 { val length: Single get() = Single.create { pp -> var ret = 0L - val client = Utils.createOkHttp(60, false).build() + val client = Utils.createOkHttp(15, false).build() try { val mediaURL: URL var m3u8Url = URL(url) @@ -51,12 +51,13 @@ class M3U8 { return@create } BufferedReader(InputStreamReader(im)).use { br -> - var line: String + var line: String? val urls = ArrayList>() var newurl: Long = 0 while (br.readLine().also { line = it } != null) { - line = line.trim { it <= ' ' } - val property = checkProperty(line) + line = line?.trim { it <= ' ' } + line ?: continue + val property = checkProperty(line ?: return@create) newurl = if (property == null) { 0 } else if (property.type == "EXT-X-STREAM-INF") { @@ -90,10 +91,11 @@ class M3U8 { val type = KeyType.NONE val key = ByteArray(16) val iv = ByteArray(16) - var line: String + var line: String? while (br.readLine().also { line = it } != null) { - line = line.trim { it <= ' ' } - val property = checkProperty(line) + line = line?.trim { it <= ' ' } + line ?: continue + val property = checkProperty(line ?: return@use) if (property != null) { if (property.type == "FILE") { val tsUrl = URL(mediaURL, line) @@ -117,6 +119,7 @@ class M3U8 { ret += if (response.isSuccessful) { val v = response.header("Content-Length") response.body.close() + response.close() if (v.isNullOrEmpty()) { pp.onSuccess(0L) return@create @@ -124,6 +127,7 @@ class M3U8 { v.toLong() } else { pp.onSuccess(0L) + response.close() return@create } j++ @@ -333,14 +337,12 @@ class M3U8 { sb.append("}") } values?.let { - if (values != null) { - sb.append(", values = [ ") - for (i in it) { - sb.append(i) - sb.append(", ") - } - sb.append("]") + sb.append(", values = [ ") + for (i in it) { + sb.append(i) + sb.append(", ") } + sb.append("]") } return sb.toString() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModKate.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModKate.kt index 89902eac2..0f46d6795 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModKate.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModKate.kt @@ -13,7 +13,6 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import java.io.IOException -import java.nio.charset.StandardCharsets import java.security.* import java.security.interfaces.RSAPrivateKey import java.util.* @@ -133,9 +132,10 @@ object TokenModKate { @Throws(IOException::class) private fun doRequest(str: String, list: List, str3: String): String { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> chain.proceed( chain.toRequestBuilder(false) @@ -181,7 +181,7 @@ object TokenModKate { instance.initSign(privateKey) instance.update( join("\n", arrayOf("com.perm.kate_new_6", str)).toByteArray( - StandardCharsets.UTF_8 + Charsets.UTF_8 ) ) Base64.encodeToString(instance.sign(), Base64.URL_SAFE or Base64.NO_WRAP) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModOfficialVK.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModOfficialVK.kt index dce93fc75..5dfae1048 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModOfficialVK.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/refresh/TokenModOfficialVK.kt @@ -14,7 +14,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import java.io.ByteArrayOutputStream import java.io.IOException -import java.nio.charset.StandardCharsets import java.security.* import java.security.interfaces.RSAPrivateKey import java.util.* @@ -163,9 +162,10 @@ object TokenModOfficialVK { @Throws(IOException::class) private fun doRequest(str: String, list: List, str3: String): String { val builder: OkHttpClient.Builder = OkHttpClient.Builder() - .readTimeout(40, TimeUnit.SECONDS) - .connectTimeout(40, TimeUnit.SECONDS) - .writeTimeout(40, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .callTimeout(15, TimeUnit.SECONDS) .addInterceptor(Interceptor { chain: Interceptor.Chain -> chain.proceed( chain.toRequestBuilder(false) @@ -211,7 +211,7 @@ object TokenModOfficialVK { instance.initSign(privateKey) instance.update( join("\n", arrayOf("com.vkontakte.android", str)).toByteArray( - StandardCharsets.UTF_8 + Charsets.UTF_8 ) ) Base64.encodeToString(instance.sign(), Base64.URL_SAFE or Base64.NO_WRAP) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/rxutils/io/AndroidSchedulers.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/rxutils/io/AndroidSchedulers.kt index 363e27b75..a43012f92 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/rxutils/io/AndroidSchedulers.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/rxutils/io/AndroidSchedulers.kt @@ -8,69 +8,68 @@ import android.os.Message import io.reactivex.rxjava3.core.Scheduler /** Android-specific Schedulers. */ -class AndroidSchedulers private constructor() { +object AndroidSchedulers { private object MainHolder { - val DEFAULT = from(Looper.getMainLooper()) + val DEFAULT = internalFrom(Looper.getMainLooper(), true) } - companion object { - private val MAIN_THREAD: Scheduler = - RxAndroidPlugins.initMainThreadScheduler { MainHolder.DEFAULT } + private val MAIN_THREAD: Scheduler = + RxAndroidPlugins.initMainThreadScheduler { MainHolder.DEFAULT } - /** - * A [Scheduler] which executes actions on the Android main thread. - * - * - * The returned scheduler will post asynchronous messages to the looper by default. - * - * @see .from - */ - fun mainThread(): Scheduler { - return RxAndroidPlugins.onMainThreadScheduler(MAIN_THREAD) - } + /** + * A [Scheduler] which executes actions on the Android main thread. + * + * + * The returned scheduler will post asynchronous messages to the looper by default. + * + * @see .from + */ + fun mainThread(): Scheduler { + return RxAndroidPlugins.onMainThreadScheduler(MAIN_THREAD) + } - /** - * A [Scheduler] which executes actions on `looper`. - * - * - * The returned scheduler will post asynchronous messages to the looper by default. - * - * @see .from - */ - fun from(looper: Looper?): Scheduler { - return from(looper, true) - } + /** + * A [Scheduler] which executes actions on `looper`. + * + * + * The returned scheduler will post asynchronous messages to the looper by default. + * + * @see .from + */ + fun from(looper: Looper?): Scheduler { + return from(looper, true) + } - /** - * A [Scheduler] which executes actions on `looper`. - * - * @param async if true, the scheduler will use async messaging on API >= 16 to avoid VSYNC - * locking. On API < 16 this value is ignored. - * @see Message.setAsynchronous - */ - @SuppressLint("NewApi") // Checking for an @hide API. - fun from(looper: Looper?, async: Boolean): Scheduler { - var pAsync = async - if (looper == null) throw NullPointerException("looper == null") + /** + * A [Scheduler] which executes actions on `looper`. + * + * @param async if true, the scheduler will use async messaging on API >= 16 to avoid VSYNC + * locking. On API < 16 this value is ignored. + * @see Message.setAsynchronous + */ + @SuppressLint("NewApi") // Checking for an @hide API. + fun from(looper: Looper?, async: Boolean): Scheduler { + if (looper == null) throw NullPointerException("looper == null") + return internalFrom(looper, async) + } + + @SuppressLint("NewApi") // Checking for an @hide API. + private fun internalFrom(looper: Looper, async: Boolean): Scheduler { + var pAsync = async - // Below code exists in androidx-core as well, but is left here rather than include an - // entire extra dependency. - // https://developer.android.com/reference/kotlin/androidx/core/os/MessageCompat?hl=en#setAsynchronous(android.os.Message,%20kotlin.Boolean) - if (pAsync && Build.VERSION.SDK_INT < 22) { - // Confirm that the method is available on this API level despite being @hide. - val message = Message.obtain() - try { - message.isAsynchronous = true - } catch (e: NoSuchMethodError) { - pAsync = false - } - message.recycle() + // Below code exists in androidx-core as well, but is left here rather than include an + // entire extra dependency. + // https://developer.android.com/reference/kotlin/androidx/core/os/MessageCompat?hl=en#setAsynchronous(android.os.Message,%20kotlin.Boolean) + if (pAsync && Build.VERSION.SDK_INT < 22) { + // Confirm that the method is available on this API level despite being @hide. + val message = Message.obtain() + try { + message.isAsynchronous = true + } catch (e: NoSuchMethodError) { + pAsync = false } - return HandlerScheduler(Handler(looper), pAsync) + message.recycle() } - } - - init { - throw AssertionError("No instances.") + return HandlerScheduler(Handler(looper), pAsync) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt new file mode 100644 index 000000000..605d6a60f --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/retrofit/HttpCodeException.kt @@ -0,0 +1,13 @@ +package dev.ragnarok.fenrir.util.serializeble.retrofit + +class HttpCodeException(val code: Int) : RuntimeException( + getMessage( + code + ) +) { + companion object { + private fun getMessage(code: Int): String { + return "HTTP $code" + } + } +} \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt index 4bb74f715..c17d5caff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt @@ -121,6 +121,7 @@ class AnimatedShapeableImageView @JvmOverloads constructor( val input = BufferedInputStream(bfr) cache.writeTempCacheFile(key, input) input.close() + response.close() cache.renameTempFile(key) } catch (e: Exception) { u.onSuccess(false) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt index e1cd09b4c..92d6af2af 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt @@ -92,6 +92,7 @@ class RLottieImageView @JvmOverloads constructor(context: Context, attrs: Attrib val input = BufferedInputStream(bfr) cache.writeTempCacheFile(url, input) input.close() + response.close() cache.renameTempFile(url) } catch (e: Exception) { u.onSuccess(false) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt index 4c36ba44c..66308a608 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt @@ -92,6 +92,7 @@ class RLottieShapeableImageView @JvmOverloads constructor( val input = BufferedInputStream(bfr) cache.writeTempCacheFile(url, input) input.close() + response.close() cache.renameTempFile(url) } catch (e: Exception) { u.onSuccess(false) diff --git a/app_fenrir/src/main/res/drawable/ic_message_check_vector.xml b/app_fenrir/src/main/res/drawable/ic_message_check_vector.xml index 9faca0b38..7ed2922e0 100644 --- a/app_fenrir/src/main/res/drawable/ic_message_check_vector.xml +++ b/app_fenrir/src/main/res/drawable/ic_message_check_vector.xml @@ -5,6 +5,6 @@ android:viewportWidth="42" android:viewportHeight="42"> diff --git a/app_fenrir/src/main/res/layout/doc_upload_entry.xml b/app_fenrir/src/main/res/layout/doc_upload_entry.xml index d5e41fe94..8ff38007a 100644 --- a/app_fenrir/src/main/res/layout/doc_upload_entry.xml +++ b/app_fenrir/src/main/res/layout/doc_upload_entry.xml @@ -35,7 +35,7 @@ android:padding="8dp" android:scaleType="centerCrop" android:src="@drawable/close" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> @@ -81,7 +81,7 @@ android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:gravity="end" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_subtitle" tools:text="id98464648" /> @@ -92,7 +92,7 @@ android:layout_marginTop="8dp" android:fontFamily="sans-serif-light" android:gravity="end" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textColorLink="?android:textColorPrimary" android:textSize="@dimen/font_size_subtitle" tools:text="Заблокирован 20 мая 2022 в 16:13" /> diff --git a/app_fenrir/src/main/res/layout/fragment_community_manager_edit.xml b/app_fenrir/src/main/res/layout/fragment_community_manager_edit.xml index 3a291bf72..5c8b168ee 100644 --- a/app_fenrir/src/main/res/layout/fragment_community_manager_edit.xml +++ b/app_fenrir/src/main/res/layout/fragment_community_manager_edit.xml @@ -71,7 +71,7 @@ android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:gravity="end" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_big_title" tools:text="Umerov Artem" /> @@ -81,7 +81,7 @@ android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:gravity="end" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_subtitle" tools:text="id98464648" /> diff --git a/app_fenrir/src/main/res/layout/fragment_document_preview.xml b/app_fenrir/src/main/res/layout/fragment_document_preview.xml index fbfbbbd34..2a8149f94 100644 --- a/app_fenrir/src/main/res/layout/fragment_document_preview.xml +++ b/app_fenrir/src/main/res/layout/fragment_document_preview.xml @@ -37,7 +37,7 @@ android:contentDescription="@null" android:padding="16dp" android:src="@drawable/file" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:loopAnimation="true" /> + app:tint="?white_color_contrast_fix" /> + app:loopAnimation="true" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> diff --git a/app_fenrir/src/main/res/layout/item_photo_gif.xml b/app_fenrir/src/main/res/layout/item_photo_gif.xml index ee3356d5e..0458ce24f 100644 --- a/app_fenrir/src/main/res/layout/item_photo_gif.xml +++ b/app_fenrir/src/main/res/layout/item_photo_gif.xml @@ -25,7 +25,7 @@ android:scaleType="centerCrop" android:src="@drawable/gif" android:visibility="gone" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> diff --git a/app_fenrir/src/main/res/layout/item_post_attachments.xml b/app_fenrir/src/main/res/layout/item_post_attachments.xml index 3a11f559b..55a987cb9 100644 --- a/app_fenrir/src/main/res/layout/item_post_attachments.xml +++ b/app_fenrir/src/main/res/layout/item_post_attachments.xml @@ -53,7 +53,7 @@ android:padding="8dp" android:scaleType="centerCrop" android:src="@drawable/close" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> diff --git a/app_fenrir/src/main/res/layout/item_selection_check.xml b/app_fenrir/src/main/res/layout/item_selection_check.xml index 1e1251f69..2826c299e 100644 --- a/app_fenrir/src/main/res/layout/item_selection_check.xml +++ b/app_fenrir/src/main/res/layout/item_selection_check.xml @@ -22,9 +22,9 @@ android:drawablePadding="4dp" android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.Material3.TitleSmall" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" app:drawableStartCompat="@drawable/check" - app:drawableTint="?white_color" + app:drawableTint="?white_color_contrast_fix" tools:text="14" /> diff --git a/app_fenrir/src/main/res/layout/item_step.xml b/app_fenrir/src/main/res/layout/item_step.xml index 807b0977d..177665f73 100644 --- a/app_fenrir/src/main/res/layout/item_step.xml +++ b/app_fenrir/src/main/res/layout/item_step.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material3.TitleSmall" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" tools:text="3" /> diff --git a/app_fenrir/src/main/res/layout/item_video_attachment.xml b/app_fenrir/src/main/res/layout/item_video_attachment.xml index 654f2e1a2..d9674db3a 100644 --- a/app_fenrir/src/main/res/layout/item_video_attachment.xml +++ b/app_fenrir/src/main/res/layout/item_video_attachment.xml @@ -42,7 +42,7 @@ android:padding="6dp" android:scaleType="centerCrop" android:src="@drawable/video" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> @@ -119,7 +119,7 @@ android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_centerVertical="true" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_additional" android:textStyle="bold" tools:text="1.5K" /> @@ -135,7 +135,7 @@ android:contentDescription="@null" android:padding="4dp" android:src="@drawable/comment" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> diff --git a/app_fenrir/src/main/res/layout/vk_photo_item_big.xml b/app_fenrir/src/main/res/layout/vk_photo_item_big.xml index d92e43324..fc37d932b 100644 --- a/app_fenrir/src/main/res/layout/vk_photo_item_big.xml +++ b/app_fenrir/src/main/res/layout/vk_photo_item_big.xml @@ -108,7 +108,7 @@ android:layout_centerVertical="true" android:layout_marginStart="2dp" android:layout_toEndOf="@id/vk_photo_item_like" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_additional" android:textStyle="bold" tools:text="4000" /> @@ -119,7 +119,7 @@ android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_centerVertical="true" - android:textColor="?white_color" + android:textColor="?white_color_contrast_fix" android:textSize="@dimen/font_size_additional" android:textStyle="bold" tools:text="1.5K" /> @@ -135,7 +135,7 @@ android:contentDescription="@null" android:padding="4dp" android:src="@drawable/comment" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> + app:tint="?white_color_contrast_fix" /> diff --git a/app_fenrir/src/main/res/layout/vk_upload_photo_item.xml b/app_fenrir/src/main/res/layout/vk_upload_photo_item.xml index a9471d516..87fdbd0a7 100644 --- a/app_fenrir/src/main/res/layout/vk_upload_photo_item.xml +++ b/app_fenrir/src/main/res/layout/vk_upload_photo_item.xml @@ -46,7 +46,7 @@ android:padding="8dp" android:scaleType="centerCrop" android:src="@drawable/close" - app:tint="?white_color" /> + app:tint="?white_color_contrast_fix" /> \ No newline at end of file diff --git a/app_fenrir/src/main/res/menu/community_manager_edit.xml b/app_fenrir/src/main/res/menu/community_manager_edit.xml index 415f86f79..0ff1e183d 100644 --- a/app_fenrir/src/main/res/menu/community_manager_edit.xml +++ b/app_fenrir/src/main/res/menu/community_manager_edit.xml @@ -6,14 +6,14 @@ android:id="@+id/action_delete" android:icon="@drawable/ic_outline_delete" android:title="@string/delete" - app:iconTint="?white_color" + app:iconTint="?white_color_contrast_fix" app:showAsAction="always" /> \ No newline at end of file diff --git a/app_fenrir/src/main/res/menu/community_option_edit.xml b/app_fenrir/src/main/res/menu/community_option_edit.xml index 8c381875c..ff686ec58 100644 --- a/app_fenrir/src/main/res/menu/community_option_edit.xml +++ b/app_fenrir/src/main/res/menu/community_option_edit.xml @@ -6,7 +6,7 @@ android:id="@+id/action_save" android:icon="@drawable/check" android:title="@string/save" - app:iconTint="?white_color" + app:iconTint="?white_color_contrast_fix" app:showAsAction="always" /> \ No newline at end of file diff --git a/app_fenrir/src/main/res/values-night-v31/styles.xml b/app_fenrir/src/main/res/values-night-v31/styles.xml index 8dc9f1a85..1244483ef 100644 --- a/app_fenrir/src/main/res/values-night-v31/styles.xml +++ b/app_fenrir/src/main/res/values-night-v31/styles.xml @@ -31,7 +31,8 @@ #CCCCCC #090909 - #FFFFFF + #FFFFFF + #000000 ?attr/colorPrimary @style/ShapeAppearance.AppTheme.SmallComponent diff --git a/app_fenrir/src/main/res/values-night/styles.xml b/app_fenrir/src/main/res/values-night/styles.xml index 0501be478..0efeaa03a 100644 --- a/app_fenrir/src/main/res/values-night/styles.xml +++ b/app_fenrir/src/main/res/values-night/styles.xml @@ -33,7 +33,8 @@ #CCCCCC #090909 - #FFFFFF + #FFFFFF + #000000 ?attr/colorPrimary @style/ShapeAppearance.AppTheme.SmallComponent @@ -295,7 +296,8 @@ #D4303030 #86448AFF #88FF0000 - #000000 + #000000 + #000000 #ECECEC #3A2C2D #524646 @@ -1047,7 +1049,8 @@ #DD1E1E1E #FFF #313131 - #FFFFFF + #FFFFFF + #000000 @style/Theme.BadgeStyle @style/CardViewMD2 @drawable/background_rectangle_stroke_filled diff --git a/app_fenrir/src/main/res/values-v31/styles.xml b/app_fenrir/src/main/res/values-v31/styles.xml index b49f1fc38..81167a1e6 100644 --- a/app_fenrir/src/main/res/values-v31/styles.xml +++ b/app_fenrir/src/main/res/values-v31/styles.xml @@ -49,7 +49,8 @@ #DDFFFFFF @android:color/white #3E3E3E - #FFFFFF + #FFFFFF + #000000 ?attr/colorPrimary @drawable/cookies_white diff --git a/app_fenrir/src/main/res/values/styles.xml b/app_fenrir/src/main/res/values/styles.xml index bd267ea32..148e7b68b 100644 --- a/app_fenrir/src/main/res/values/styles.xml +++ b/app_fenrir/src/main/res/values/styles.xml @@ -59,7 +59,8 @@ #DDFFFFFF @android:color/white #3E3E3E - #FFFFFF + #FFFFFF + #000000 ?attr/colorPrimary @drawable/cookies_white @@ -272,6 +273,7 @@ #4D4D4D #000000 #CFCFCF + #FFFFFF + - - diff --git a/material/java/com/google/android/material/card/res/values/tokens.xml b/material/java/com/google/android/material/card/res/values/tokens.xml deleted file mode 100644 index b5cbb1073..000000000 --- a/material/java/com/google/android/material/card/res/values/tokens.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - ?attr/colorSurfaceVariant - @dimen/m3_sys_elevation_level0 - ?attr/shapeAppearanceCornerMedium - - 24dp - - @dimen/m3_sys_state_hover_state_layer_opacity - - @dimen/m3_sys_state_focus_state_layer_opacity - - @dimen/m3_sys_state_pressed_state_layer_opacity - - @dimen/m3_sys_state_dragged_state_layer_opacity - - - - ?attr/colorSurface - @dimen/m3_sys_elevation_level0 - ?attr/shapeAppearanceCornerMedium - - 1dp - ?attr/colorOutline - - 24dp - - ?attr/colorOutline - 0.12 - - ?attr/colorOutline - - ?attr/colorOnSurface - - ?attr/colorOutline - - ?attr/colorOutline - - - - ?attr/colorSurface - @dimen/m3_sys_elevation_level1 - ?attr/shapeAppearanceCornerMedium - - 24dp - - diff --git a/material/java/com/google/android/material/checkbox/res/values/tokens.xml b/material/java/com/google/android/material/checkbox/res/values/tokens.xml index 65763a101..1b453c079 100644 --- a/material/java/com/google/android/material/checkbox/res/values/tokens.xml +++ b/material/java/com/google/android/material/checkbox/res/values/tokens.xml @@ -15,11 +15,10 @@ ~ limitations under the License. --> - + - - + ?attr/colorOnSurfaceVariant ?attr/colorPrimary @@ -32,5 +31,4 @@ 0.38 ?attr/colorSurface - diff --git a/material/java/com/google/android/material/chip/res/values/tokens.xml b/material/java/com/google/android/material/chip/res/values/tokens.xml index 162c07f29..6ea25b0df 100644 --- a/material/java/com/google/android/material/chip/res/values/tokens.xml +++ b/material/java/com/google/android/material/chip/res/values/tokens.xml @@ -15,11 +15,10 @@ ~ limitations under the License. --> - + - - + 24dp @@ -32,7 +31,7 @@ 18dp - + ?attr/shapeAppearanceCornerSmall 18dp @@ -40,25 +39,28 @@ @dimen/m3_sys_elevation_level0 1dp @dimen/m3_sys_elevation_level1 + ?attr/textAppearanceLabelLarge - + ?attr/shapeAppearanceCornerSmall 32dp @dimen/m3_sys_elevation_level0 1dp @dimen/m3_sys_elevation_level1 + ?attr/textAppearanceLabelLarge 18dp - + ?attr/shapeAppearanceCornerSmall 32dp + @dimen/m3_sys_elevation_level0 1dp @dimen/m3_sys_elevation_level1 @@ -66,5 +68,4 @@ ?attr/textAppearanceLabelLarge 18dp - diff --git a/material/java/com/google/android/material/color/res-public/values/public.xml b/material/java/com/google/android/material/color/res-public/values/public.xml index 0fa9c06b7..28ffbf290 100644 --- a/material/java/com/google/android/material/color/res-public/values/public.xml +++ b/material/java/com/google/android/material/color/res-public/values/public.xml @@ -39,14 +39,6 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml deleted file mode 100644 index da1304221..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral12.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml deleted file mode 100644 index 2cef816d5..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral17.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml deleted file mode 100644 index a4596ac6e..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral22.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml deleted file mode 100644 index 0cf131c1d..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral24.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml deleted file mode 100644 index 16a3dc130..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral4.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml deleted file mode 100644 index 16260cc9c..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral6.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml deleted file mode 100644 index a844e548d..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral87.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml deleted file mode 100644 index d64ea0cdd..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral92.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml deleted file mode 100644 index 742393113..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral94.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml deleted file mode 100644 index e8994b795..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral96.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml b/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml deleted file mode 100644 index bdd99159e..000000000 --- a/material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_neutral98.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/values-v31/tokens.xml b/material/java/com/google/android/material/color/res/values-v31/tokens.xml index 5003fe575..d5f2bc31d 100644 --- a/material/java/com/google/android/material/color/res/values-v31/tokens.xml +++ b/material/java/com/google/android/material/color/res/values-v31/tokens.xml @@ -15,11 +15,10 @@ ~ limitations under the License. --> - + - - + @color/m3_ref_palette_dynamic_primary80 @color/m3_ref_palette_dynamic_primary30 @@ -37,13 +36,6 @@ @color/m3_ref_palette_dynamic_tertiary20 @color/m3_ref_palette_dynamic_tertiary90 - @color/m3_ref_palette_dynamic_neutral6 - @color/m3_ref_palette_dynamic_neutral24 - @color/m3_ref_palette_dynamic_neutral12 - @color/m3_ref_palette_dynamic_neutral10 - @color/m3_ref_palette_dynamic_neutral17 - @color/m3_ref_palette_dynamic_neutral4 - @color/m3_ref_palette_dynamic_neutral22 @color/m3_ref_palette_dynamic_neutral10 @color/m3_ref_palette_dynamic_neutral_variant30 @color/m3_ref_palette_dynamic_neutral90 @@ -57,7 +49,7 @@ @color/m3_ref_palette_dynamic_neutral_variant60 @color/m3_ref_palette_dynamic_neutral_variant30 - + @color/m3_ref_palette_dynamic_primary40 @color/m3_ref_palette_dynamic_primary90 @@ -75,13 +67,6 @@ @color/m3_ref_palette_dynamic_tertiary100 @color/m3_ref_palette_dynamic_tertiary10 - @color/m3_ref_palette_dynamic_neutral87 - @color/m3_ref_palette_dynamic_neutral98 - @color/m3_ref_palette_dynamic_neutral94 - @color/m3_ref_palette_dynamic_neutral96 - @color/m3_ref_palette_dynamic_neutral92 - @color/m3_ref_palette_dynamic_neutral100 - @color/m3_ref_palette_dynamic_neutral90 @color/m3_ref_palette_dynamic_neutral99 @color/m3_ref_palette_dynamic_neutral_variant90 @color/m3_ref_palette_dynamic_neutral10 @@ -95,7 +80,7 @@ @color/m3_ref_palette_dynamic_neutral_variant50 @color/m3_ref_palette_dynamic_neutral_variant80 - + @android:color/system_accent1_0 @android:color/system_accent1_10 @@ -166,5 +151,4 @@ @android:color/system_neutral2_800 @android:color/system_neutral2_900 @android:color/system_neutral2_1000 - diff --git a/material/java/com/google/android/material/color/res/values/attrs.xml b/material/java/com/google/android/material/color/res/values/attrs.xml index b7a3ff908..e7e2248a5 100644 --- a/material/java/com/google/android/material/color/res/values/attrs.xml +++ b/material/java/com/google/android/material/color/res/values/attrs.xml @@ -70,22 +70,6 @@ - - - - - - - - - - - - - - - - diff --git a/material/java/com/google/android/material/color/res/values/tokens.xml b/material/java/com/google/android/material/color/res/values/tokens.xml index c5ea89515..b82db0d1a 100644 --- a/material/java/com/google/android/material/color/res/values/tokens.xml +++ b/material/java/com/google/android/material/color/res/values/tokens.xml @@ -15,11 +15,10 @@ ~ limitations under the License. --> - + - - + @color/m3_ref_palette_primary80 @color/m3_ref_palette_primary30 @@ -37,13 +36,6 @@ @color/m3_ref_palette_tertiary20 @color/m3_ref_palette_tertiary90 - @color/m3_ref_palette_neutral6 - @color/m3_ref_palette_neutral24 - @color/m3_ref_palette_neutral12 - @color/m3_ref_palette_neutral10 - @color/m3_ref_palette_neutral17 - @color/m3_ref_palette_neutral4 - @color/m3_ref_palette_neutral22 @color/m3_ref_palette_neutral10 @color/m3_ref_palette_neutral_variant30 @color/m3_ref_palette_neutral90 @@ -63,7 +55,7 @@ @color/m3_ref_palette_error20 @color/m3_ref_palette_error90 - + @color/m3_ref_palette_primary40 @color/m3_ref_palette_primary90 @@ -81,13 +73,6 @@ @color/m3_ref_palette_tertiary100 @color/m3_ref_palette_tertiary10 - @color/m3_ref_palette_neutral87 - @color/m3_ref_palette_neutral98 - @color/m3_ref_palette_neutral94 - @color/m3_ref_palette_neutral96 - @color/m3_ref_palette_neutral92 - @color/m3_ref_palette_neutral100 - @color/m3_ref_palette_neutral90 @color/m3_ref_palette_neutral99 @color/m3_ref_palette_neutral_variant90 @color/m3_ref_palette_neutral10 @@ -107,7 +92,7 @@ @color/m3_ref_palette_error100 @color/m3_ref_palette_error10 - + #ffffffff #fffffbfe @@ -167,27 +152,16 @@ #ffffffff #fffffbfe - #fffdf8fd - #fff7f2f7 #fff4eff4 - #fff1ecf1 - #ffece7ec #ffe6e1e5 - #ffddd8dd #ffc9c5ca #ffaeaaae #ff939094 #ff787579 #ff605d62 #ff484649 - #ff313033 - #ff313033 #ff313033 - #ff2b292d - #ff201f23 #ff1c1b1f - #ff141317 - #ff0e0e11 #ff000000 #ffffffff @@ -206,5 +180,4 @@ #ffffffff #ff000000 - diff --git a/material/java/com/google/android/material/color/utilities/ColorUtils.java b/material/java/com/google/android/material/color/utilities/ColorUtils.java index 5c37a228b..817f772b6 100644 --- a/material/java/com/google/android/material/color/utilities/ColorUtils.java +++ b/material/java/com/google/android/material/color/utilities/ColorUtils.java @@ -192,21 +192,6 @@ public static double yFromLstar(double lstar) { return 100.0 * labInvf((lstar + 16.0) / 116.0); } - /** - * Converts a Y value to an L* value. - * - *

L* in L*a*b* and Y in XYZ measure the same quantity, luminance. - * - *

L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a - * logarithmic scale. - * - * @param y Y in XYZ - * @return L* in L*a*b* - */ - public static double lstarFromY(double y) { - return labF(y / 100.0) * 116.0 - 16.0; - } - /** * Linearizes an RGB component. * diff --git a/material/java/com/google/android/material/color/utilities/Contrast.java b/material/java/com/google/android/material/color/utilities/Contrast.java deleted file mode 100644 index bccd63a58..000000000 --- a/material/java/com/google/android/material/color/utilities/Contrast.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static java.lang.Math.max; - -import androidx.annotation.RestrictTo; - -/** - * Color science for contrast utilities. - * - *

Utility methods for calculating contrast given two colors, or calculating a color given one - * color and a contrast ratio. - * - *

Contrast ratio is calculated using XYZ's Y. When linearized to match human perception, Y - * becomes HCT's tone and L*a*b*'s' L*. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public final class Contrast { - // The minimum contrast ratio of two colors. - // Contrast ratio equation = lighter + 5 / darker + 5, if lighter == darker, ratio == 1. - public static final double RATIO_MIN = 1.0; - - // The maximum contrast ratio of two colors. - // Contrast ratio equation = lighter + 5 / darker + 5. Lighter and darker scale from 0 to 100. - // If lighter == 100, darker = 0, ratio == 21. - public static final double RATIO_MAX = 21.0; - public static final double RATIO_30 = 3.0; - public static final double RATIO_45 = 4.5; - public static final double RATIO_70 = 7.0; - - // Given a color and a contrast ratio to reach, the luminance of a color that reaches that ratio - // with the color can be calculated. However, that luminance may not contrast as desired, i.e. the - // contrast ratio of the input color and the returned luminance may not reach the contrast ratio - // asked for. - // - // When the desired contrast ratio and the result contrast ratio differ by more than this amount, - // an error value should be returned, or the method should be documented as 'unsafe', meaning, - // it will return a valid luminance but that luminance may not meet the requested contrast ratio. - // - // 0.04 selected because it ensures the resulting ratio rounds to the same tenth. - private static final double CONTRAST_RATIO_EPSILON = 0.04; - - // Color spaces that measure luminance, such as Y in XYZ, L* in L*a*b*, or T in HCT, are known as - // perceptual accurate color spaces. - // - // To be displayed, they must gamut map to a "display space", one that has a defined limit on the - // number of colors. Display spaces include sRGB, more commonly understood as RGB/HSL/HSV/HSB. - // Gamut mapping is undefined and not defined by the color space. Any gamut mapping algorithm must - // choose how to sacrifice accuracy in hue, saturation, and/or lightness. - // - // A principled solution is to maintain lightness, thus maintaining contrast/a11y, maintain hue, - // thus maintaining aesthetic intent, and reduce chroma until the color is in gamut. - // - // HCT chooses this solution, but, that doesn't mean it will _exactly_ matched desired lightness, - // if only because RGB is quantized: RGB is expressed as a set of integers: there may be an RGB - // color with, for example, 47.892 lightness, but not 47.891. - // - // To allow for this inherent incompatibility between perceptually accurate color spaces and - // display color spaces, methods that take a contrast ratio and luminance, and return a luminance - // that reaches that contrast ratio for the input luminance, purposefully darken/lighten their - // result such that the desired contrast ratio will be reached even if inaccuracy is introduced. - // - // 0.4 is generous, ex. HCT requires much less delta. It was chosen because it provides a rough - // guarantee that as long as a percetual color space gamut maps lightness such that the resulting - // lightness rounds to the same as the requested, the desired contrast ratio will be reached. - private static final double LUMINANCE_GAMUT_MAP_TOLERANCE = 0.4; - - private Contrast() {} - - /** - * Contrast ratio is a measure of legibility, its used to compare the lightness of two colors. - * This method is used commonly in industry due to its use by WCAG. - * - *

To compare lightness, the colors are expressed in the XYZ color space, where Y is lightness, - * also known as relative luminance. - * - *

The equation is ratio = lighter Y + 5 / darker Y + 5. - */ - public static double ratioOfYs(double y1, double y2) { - final double lighter = max(y1, y2); - final double darker = (lighter == y2) ? y1 : y2; - return (lighter + 5.0) / (darker + 5.0); - } - - /** - * Contrast ratio of two tones. T in HCT, L* in L*a*b*. Also known as luminance or perpectual - * luminance. - * - *

Contrast ratio is defined using Y in XYZ, relative luminance. However, relative luminance is - * linear to number of photons, not to perception of lightness. Perceptual luminance, L* in - * L*a*b*, T in HCT, is. Designers prefer color spaces with perceptual luminance since they're - * accurate to the eye. - * - *

Y and L* are pure functions of each other, so it possible to use perceptually accurate color - * spaces, and measure contrast, and measure contrast in a much more understandable way: instead - * of a ratio, a linear difference. This allows a designer to determine what they need to adjust a - * color's lightness to in order to reach their desired contrast, instead of guessing & checking - * with hex codes. - */ - public static double ratioOfTones(double t1, double t2) { - return ratioOfYs(ColorUtils.yFromLstar(t1), ColorUtils.yFromLstar(t2)); - } - - /** - * Returns T in HCT, L* in L*a*b* >= tone parameter that ensures ratio with input T/L*. Returns -1 - * if ratio cannot be achieved. - * - * @param tone Tone return value must contrast with. - * @param ratio Desired contrast ratio of return value and tone parameter. - */ - public static double lighter(double tone, double ratio) { - if (tone < 0.0 || tone > 100.0) { - return -1.0; - } - // Invert the contrast ratio equation to determine lighter Y given a ratio and darker Y. - final double darkY = ColorUtils.yFromLstar(tone); - final double lightY = ratio * (darkY + 5.0) - 5.0; - if (lightY < 0.0 || lightY > 100.0) { - return -1.0; - } - final double realContrast = ratioOfYs(lightY, darkY); - final double delta = Math.abs(realContrast - ratio); - if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) { - return -1.0; - } - - final double returnValue = ColorUtils.lstarFromY(lightY) + LUMINANCE_GAMUT_MAP_TOLERANCE; - // NOMUTANTS--important validation step; functions it is calling may change implementation. - if (returnValue < 0 || returnValue > 100) { - return -1.0; - } - return returnValue; - } - - /** - * Tone >= tone parameter that ensures ratio. 100 if ratio cannot be achieved. - * - *

This method is unsafe because the returned value is guaranteed to be in bounds, but, the in - * bounds return value may not reach the desired ratio. - * - * @param tone Tone return value must contrast with. - * @param ratio Desired contrast ratio of return value and tone parameter. - */ - public static double lighterUnsafe(double tone, double ratio) { - double lighterSafe = lighter(tone, ratio); - return lighterSafe < 0.0 ? 100.0 : lighterSafe; - } - - /** - * Returns T in HCT, L* in L*a*b* <= tone parameter that ensures ratio with input T/L*. Returns -1 - * if ratio cannot be achieved. - * - * @param tone Tone return value must contrast with. - * @param ratio Desired contrast ratio of return value and tone parameter. - */ - public static double darker(double tone, double ratio) { - if (tone < 0.0 || tone > 100.0) { - return -1.0; - } - // Invert the contrast ratio equation to determine darker Y given a ratio and lighter Y. - final double lightY = ColorUtils.yFromLstar(tone); - final double darkY = ((lightY + 5.0) / ratio) - 5.0; - if (darkY < 0.0 || darkY > 100.0) { - return -1.0; - } - final double realContrast = ratioOfYs(lightY, darkY); - final double delta = Math.abs(realContrast - ratio); - if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) { - return -1.0; - } - - // For information on 0.4 constant, see comment in lighter(tone, ratio). - final double returnValue = ColorUtils.lstarFromY(darkY) - LUMINANCE_GAMUT_MAP_TOLERANCE; - // NOMUTANTS--important validation step; functions it is calling may change implementation. - if (returnValue < 0 || returnValue > 100) { - return -1.0; - } - return returnValue; - } - - /** - * Tone <= tone parameter that ensures ratio. 0 if ratio cannot be achieved. - * - *

This method is unsafe because the returned value is guaranteed to be in bounds, but, the in - * bounds return value may not reach the desired ratio. - * - * @param tone Tone return value must contrast with. - * @param ratio Desired contrast ratio of return value and tone parameter. - */ - public static double darkerUnsafe(double tone, double ratio) { - double darkerSafe = darker(tone, ratio); - return max(0.0, darkerSafe); - } -} diff --git a/material/java/com/google/android/material/color/utilities/DynamicColor.java b/material/java/com/google/android/material/color/utilities/DynamicColor.java deleted file mode 100644 index 100114e1c..000000000 --- a/material/java/com/google/android/material/color/utilities/DynamicColor.java +++ /dev/null @@ -1,583 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import androidx.annotation.RestrictTo; -import java.util.HashMap; -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * A color that adjusts itself based on UI state, represented by DynamicScheme. - * - *

This color automatically adjusts to accommodate a desired contrast level, or other adjustments - * such as differing in light mode versus dark mode, or what the theme is, or what the color that - * produced the theme is, etc. - * - *

Colors without backgrounds do not change tone when contrast changes. Colors with backgrounds - * become closer to their background as contrast lowers, and further when contrast increases. - * - *

Prefer the static constructors. They provide a much more simple interface, such as requiring - * just a hexcode, or just a hexcode and a background. - * - *

Ultimately, each component necessary for calculating a color, adjusting it for a desired - * contrast level, and ensuring it has a certain lightness/tone difference from another color, is - * provided by a function that takes a DynamicScheme and returns a value. This ensures ultimate - * flexibility, any desired behavior of a color for any design system, but it usually unnecessary. - * See the default constructor for more information. - * - * @hide - */ -// Prevent lint for Function.apply not being available on Android before API level 14 (4.0.1). -// "AndroidJdkLibsChecker" for Function, "NewApi" for Function.apply(). -// A java_library Bazel rule with an Android constraint cannot skip these warnings without this -// annotation; another solution would be to create an android_library rule and supply -// AndroidManifest with an SDK set higher than 14. -@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"}) -@RestrictTo(LIBRARY_GROUP) -public final class DynamicColor { - public final Function hue; - public final Function chroma; - public final Function tone; - public final Function opacity; - - public final Function background; - public final Function toneMinContrast; - public final Function toneMaxContrast; - public final Function toneDeltaConstraint; - - private final HashMap hctCache = new HashMap<>(); - - /** - * The base constructor for DynamicColor. - * - *

Functional arguments allow overriding without risks that come with subclasses. _Strongly_ - * prefer using one of the static convenience constructors. This class is arguably too flexible to - * ensure it can support any scenario. - * - *

For example, the default behavior of adjust tone at max contrast to be at a 7.0 ratio with - * its background is principled and matches a11y guidance. That does not mean it's the desired - * approach for _every_ design system, and every color pairing, always, in every case. - * - * @param hue given DynamicScheme, return the hue in HCT of the output color. - * @param chroma given DynamicScheme, return chroma in HCT of the output color. - * @param tone given DynamicScheme, return tone in HCT of the output color. - * @param background given DynamicScheme, return the DynamicColor that is the background of this - * DynamicColor. When this is provided, automated adjustments to lower and raise contrast are - * made. - * @param toneMinContrast given DynamicScheme, return tone in HCT/L* in L*a*b* this color should - * be at minimum contrast. See toneMinContrastDefault for the default behavior, and strongly - * consider using it unless you have strong opinions on a11y. The static constructors use it. - * @param toneMaxContrast given DynamicScheme, return tone in HCT/L* in L*a*b* this color should - * be at maximum contrast. See toneMaxContrastDefault for the default behavior, and strongly - * consider using it unless you have strong opinions on a11y. The static constructors use it. - * @param toneDeltaConstraint given DynamicScheme, return a ToneDeltaConstraint instance that - * describes a requirement that this DynamicColor must always have some difference in tone/L* - * from another DynamicColor.
- * Unlikely to be useful unless a design system has some distortions where colors that don't - * have a background/foreground relationship must have _some_ difference in tone, yet, not - * enough difference to create meaningful contrast. - */ - public DynamicColor( - Function hue, - Function chroma, - Function tone, - Function opacity, - Function background, - Function toneMinContrast, - Function toneMaxContrast, - Function toneDeltaConstraint) { - this.hue = hue; - this.chroma = chroma; - this.tone = tone; - this.opacity = opacity; - this.background = background; - this.toneMinContrast = toneMinContrast; - this.toneMaxContrast = toneMaxContrast; - this.toneDeltaConstraint = toneDeltaConstraint; - } - - /** - * Create a DynamicColor from a hex code. - * - *

Result has no background; thus no support for increasing/decreasing contrast for a11y. - */ - public static DynamicColor fromArgb(int argb) { - final Hct hct = Hct.fromInt(argb); - final TonalPalette palette = TonalPalette.fromInt(argb); - return DynamicColor.fromPalette((s) -> palette, (s) -> hct.getTone()); - } - - /** - * Create a DynamicColor from just a hex code. - * - *

Result has no background; thus cannot support increasing/decreasing contrast for a11y. - * - * @param argb A hex code. - * @param tone Function that provides a tone given DynamicScheme. Useful for adjusting for dark - * vs. light mode. - */ - public static DynamicColor fromArgb(int argb, Function tone) { - return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone); - } - - /** - * Create a DynamicColor. - * - *

If you don't understand HCT fully, or your design team doesn't, but wants support for - * automated contrast adjustment, this method is _extremely_ useful: you can take a standard - * design system expressed as hex codes, create DynamicColors corresponding to each color, and - * then wire up backgrounds. - * - *

If the design system uses the same hex code on multiple backgrounds, define that in multiple - * DynamicColors so that the background is accurate for each one. If you define a DynamicColor - * with one background, and actually use it on another, DynamicColor can't guarantee contrast. For - * example, if you use a color on both black and white, increasing the contrast on one necessarily - * decreases contrast of the other. - * - * @param argb A hex code. - * @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light mode) - * @param background Function that provides background DynamicColor given DynamicScheme. Useful - * for contrast, given a background, colors can adjust to increase/decrease contrast. - */ - public static DynamicColor fromArgb( - int argb, - Function tone, - Function background) { - return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone, background); - } - - /** - * Create a DynamicColor from: - * - * @param argb A hex code. - * @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light mode) - * @param background Function that provides background DynamicColor given DynamicScheme. Useful - * for contrast, given a background, colors can adjust to increase/decrease contrast. - * @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme. - * Useful for ensuring lightness difference between colors that don't _require_ contrast or - * have a formal background/foreground relationship. - */ - public static DynamicColor fromArgb( - int argb, - Function tone, - Function background, - Function toneDeltaConstraint) { - return DynamicColor.fromPalette( - (s) -> TonalPalette.fromInt(argb), tone, background, toneDeltaConstraint); - } - - /** - * Create a DynamicColor. - * - * @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette is - * defined by a hue and chroma, so this replaces the need to specify hue/chroma. By providing - * a tonal palette, when contrast adjustments are made, intended chroma can be preserved. For - * example, at T/L* 90, there is a significant limit to the amount of chroma. There is no - * colorful red, a red that light is pink. By preserving the _intended_ chroma if lightness - * lowers for contrast adjustments, the intended chroma is restored. - * @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light mode) - */ - public static DynamicColor fromPalette( - Function palette, Function tone) { - return fromPalette(palette, tone, null, null); - } - - /** - * Create a DynamicColor. - * - * @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette is - * defined by a hue and chroma, so this replaces the need to specify hue/chroma. By providing - * a tonal palette, when contrast adjustments are made, intended chroma can be preserved. For - * example, at T/L* 90, there is a significant limit to the amount of chroma. There is no - * colorful red, a red that light is pink. By preserving the _intended_ chroma if lightness - * lowers for contrast adjustments, the intended chroma is restored. - * @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light mode) - * @param background Function that provides background DynamicColor given DynamicScheme. Useful - * for contrast, given a background, colors can adjust to increase/decrease contrast. - */ - public static DynamicColor fromPalette( - Function palette, - Function tone, - Function background) { - return fromPalette(palette, tone, background, null); - } - - /** - * Create a DynamicColor. - * - * @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette is - * defined by a hue and chroma, so this replaces the need to specify hue/chroma. By providing - * a tonal palette, when contrast adjustments are made, intended chroma can be preserved. For - * example, at T/L* 90, there is a significant limit to the amount of chroma. There is no - * colorful red, a red that light is pink. By preserving the _intended_ chroma if lightness - * lowers for contrast adjustments, the intended chroma is restored. - * @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light mode) - * @param background Function that provides background DynamicColor given DynamicScheme. Useful - * for contrast, given a background, colors can adjust to increase/decrease contrast. - * @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme. - * Useful for ensuring lightness difference between colors that don't _require_ contrast or - * have a formal background/foreground relationship. - */ - public static DynamicColor fromPalette( - Function palette, - Function tone, - Function background, - Function toneDeltaConstraint) { - return new DynamicColor( - scheme -> palette.apply(scheme).getHue(), - scheme -> palette.apply(scheme).getChroma(), - tone, - null, - background, - scheme -> toneMinContrastDefault(tone, background, scheme, toneDeltaConstraint), - scheme -> toneMaxContrastDefault(tone, background, scheme, toneDeltaConstraint), - toneDeltaConstraint); - } - - public int getArgb(DynamicScheme scheme) { - final int argb = getHct(scheme).toInt(); - if (opacity == null) { - return argb; - } - final double percentage = opacity.apply(scheme); - final int alpha = MathUtils.clampInt(0, 255, (int) Math.round(percentage * 255)); - return (argb & 0x00ffffff) | (alpha << 24); - } - - public Hct getHct(DynamicScheme scheme) { - final Hct cachedAnswer = hctCache.get(scheme); - if (cachedAnswer != null) { - return cachedAnswer; - } - // This is crucial for aesthetics: we aren't simply the taking the standard color - // and changing its tone for contrast. Rather, we find the tone for contrast, then - // use the specified chroma from the palette to construct a new color. - // - // For example, this enables colors with standard tone of T90, which has limited chroma, to - // "recover" intended chroma as contrast increases. - final Hct answer = Hct.from(hue.apply(scheme), chroma.apply(scheme), getTone(scheme)); - // NOMUTANTS--trivial test with onerous dependency injection requirement. - if (hctCache.size() > 4) { - hctCache.clear(); - } - // NOMUTANTS--trivial test with onerous dependency injection requirement. - hctCache.put(scheme, answer); - return answer; - } - - double getTone(DynamicScheme scheme) { - double answer = tone.apply(scheme); - - final boolean decreasingContrast = scheme.contrastLevel < 0.0; - if (scheme.contrastLevel != 0.0) { - final double startTone = tone.apply(scheme); - final double endTone = - decreasingContrast ? toneMinContrast.apply(scheme) : toneMaxContrast.apply(scheme); - final double delta = (endTone - startTone) * Math.abs(scheme.contrastLevel); - answer = delta + startTone; - } - - final DynamicColor bgDynamicColor = background == null ? null : background.apply(scheme); - double minRatio = Contrast.RATIO_MIN; - double maxRatio = Contrast.RATIO_MAX; - if (bgDynamicColor != null) { - final boolean bgHasBg = - bgDynamicColor.background != null && bgDynamicColor.background.apply(scheme) == null; - final double standardRatio = - Contrast.ratioOfTones(tone.apply(scheme), bgDynamicColor.tone.apply(scheme)); - if (decreasingContrast) { - final double minContrastRatio = - Contrast.ratioOfTones( - toneMinContrast.apply(scheme), bgDynamicColor.toneMinContrast.apply(scheme)); - minRatio = bgHasBg ? 1.0 : minContrastRatio; - maxRatio = standardRatio; - } else { - final double maxContrastRatio = - Contrast.ratioOfTones( - toneMaxContrast.apply(scheme), bgDynamicColor.toneMaxContrast.apply(scheme)); - minRatio = !bgHasBg ? 1.0 : min(maxContrastRatio, standardRatio); - maxRatio = !bgHasBg ? 21.0 : max(maxContrastRatio, standardRatio); - } - } - - final double finalMinRatio = minRatio; - final double finalMaxRatio = maxRatio; - final double finalAnswer = answer; - answer = - calculateDynamicTone( - scheme, - this.tone, - (dynamicColor) -> dynamicColor.getTone(scheme), - (a, b) -> finalAnswer, - (s) -> bgDynamicColor, - toneDeltaConstraint, - (s) -> finalMinRatio, - (s) -> finalMaxRatio); - - return answer; - } - - /** - * The default algorithm for calculating the tone of a color at minimum contrast.
- * If the original contrast ratio was >= 7.0, reach contrast 4.5.
- * If the original contrast ratio was >= 3.0, reach contrast 3.0.
- * If the original contrast ratio was < 3.0, reach that ratio. - */ - public static double toneMinContrastDefault( - Function tone, - Function background, - DynamicScheme scheme, - Function toneDeltaConstraint) { - return DynamicColor.calculateDynamicTone( - scheme, - tone, - c -> c.toneMinContrast.apply(scheme), - (stdRatio, bgTone) -> { - double answer = tone.apply(scheme); - if (stdRatio >= Contrast.RATIO_70) { - answer = contrastingTone(bgTone, Contrast.RATIO_45); - } else if (stdRatio >= Contrast.RATIO_30) { - answer = contrastingTone(bgTone, Contrast.RATIO_30); - } else { - final boolean backgroundHasBackground = - background != null - && background.apply(scheme) != null - && background.apply(scheme).background != null - && background.apply(scheme).background.apply(scheme) != null; - if (backgroundHasBackground) { - answer = contrastingTone(bgTone, stdRatio); - } - } - return answer; - }, - background, - toneDeltaConstraint, - null, - standardRatio -> standardRatio); - } - - /** - * The default algorithm for calculating the tone of a color at maximum contrast.
- * If the color's background has a background, reach contrast 7.0.
- * If it doesn't, maintain the original contrast ratio.
- * - *

This ensures text on surfaces maintains its original, often detrimentally excessive, - * contrast ratio. But, text on buttons can soften to not have excessive contrast. - * - *

Historically, digital design uses pure whites and black for text and surfaces. It's too much - * of a jump at this point in history to introduce a dynamic contrast system _and_ insist that - * text always had excessive contrast and should reach 7.0, it would deterimentally affect desire - * to understand and use dynamic contrast. - */ - public static double toneMaxContrastDefault( - Function tone, - Function background, - DynamicScheme scheme, - Function toneDeltaConstraint) { - return DynamicColor.calculateDynamicTone( - scheme, - tone, - c -> c.toneMaxContrast.apply(scheme), - (stdRatio, bgTone) -> { - final boolean backgroundHasBackground = - background != null - && background.apply(scheme) != null - && background.apply(scheme).background != null - && background.apply(scheme).background.apply(scheme) != null; - if (backgroundHasBackground) { - return contrastingTone(bgTone, Contrast.RATIO_70); - } else { - return contrastingTone(bgTone, max(Contrast.RATIO_70, stdRatio)); - } - }, - background, - toneDeltaConstraint, - null, - null); - } - - /** - * Core method for calculating a tone for under dynamic contrast. - * - *

It enforces important properties:
- * #1. Desired contrast ratio is reached.
- * As contrast increases from standard to max, the tones involved should always be at least the - * standard ratio. For example, if a button is T90, and button text is T0, and the button is T0 at - * max contrast, the button text cannot simply linearly interpolate from T0 to T100, or at some - * point they'll both be at the same tone. - * - *

#2. Enable light foregrounds on midtones.
- * The eye prefers light foregrounds on T50 to T60, possibly up to T70, but, contrast ratio 4.5 - * can't be reached with T100 unless the foreground is T50. Contrast ratio 4.5 is crucial, it - * represents 'readable text', i.e. text smaller than ~40 dp / 1/4". So, if a tone is between T50 - * and T60, it is proactively changed to T49 to enable light foregrounds. - * - *

#3. Ensure tone delta with another color.
- * In design systems, there may be colors that don't have a pure background/foreground - * relationship, but, do require different tones for visual differentiation. ToneDeltaConstraint - * models this requirement, and DynamicColor enforces it. - */ - public static double calculateDynamicTone( - DynamicScheme scheme, - Function toneStandard, - Function toneToJudge, - BiFunction desiredTone, - Function background, - Function toneDeltaConstraint, - Function minRatio, - Function maxRatio) { - // Start with the tone with no adjustment for contrast. - // If there is no background, don't perform any adjustment, return immediately. - final double toneStd = toneStandard.apply(scheme); - double answer = toneStd; - final DynamicColor bgDynamic = background == null ? null : background.apply(scheme); - if (bgDynamic == null) { - return answer; - } - final double bgToneStd = bgDynamic.tone.apply(scheme); - final double stdRatio = Contrast.ratioOfTones(toneStd, bgToneStd); - - // If there is a background, determine its tone after contrast adjustment. - // Then, calculate the foreground tone that ensures the caller's desired contrast ratio is met. - final double bgTone = toneToJudge.apply(bgDynamic); - final double myDesiredTone = desiredTone.apply(stdRatio, bgTone); - final double currentRatio = Contrast.ratioOfTones(bgTone, myDesiredTone); - final double minRatioRealized = - minRatio == null - ? Contrast.RATIO_MIN - : minRatio.apply(stdRatio) == null ? Contrast.RATIO_MIN : minRatio.apply(stdRatio); - final double maxRatioRealized = - maxRatio == null - ? Contrast.RATIO_MAX - : maxRatio.apply(stdRatio) == null ? Contrast.RATIO_MAX : maxRatio.apply(stdRatio); - final double desiredRatio = - MathUtils.clampDouble(minRatioRealized, maxRatioRealized, currentRatio); - if (desiredRatio == currentRatio) { - answer = myDesiredTone; - } else { - answer = DynamicColor.contrastingTone(bgTone, desiredRatio); - } - - // If the background has no background, adjust the foreground tone to ensure that - // it is dark enough to have a light foreground. - if (bgDynamic.background == null || bgDynamic.background.apply(scheme) == null) { - answer = DynamicColor.enableLightForeground(answer); - } - - // If the caller has specified a constraint where it must have a certain tone distance from - // another color, enforce that constraint. - answer = ensureToneDelta(answer, toneStd, scheme, toneDeltaConstraint, toneToJudge); - - return answer; - } - - static double ensureToneDelta( - double tone, - double toneStandard, - DynamicScheme scheme, - Function toneDeltaConstraint, - Function toneToDistanceFrom) { - final ToneDeltaConstraint constraint = - (toneDeltaConstraint == null ? null : toneDeltaConstraint.apply(scheme)); - if (constraint == null) { - return tone; - } - - final double requiredDelta = constraint.delta; - final double keepAwayTone = toneToDistanceFrom.apply(constraint.keepAway); - final double delta = Math.abs(tone - keepAwayTone); - if (delta >= requiredDelta) { - return tone; - } - switch (constraint.keepAwayPolarity) { - case DARKER: - return MathUtils.clampDouble(0, 100, keepAwayTone + requiredDelta); - case LIGHTER: - return MathUtils.clampDouble(0, 100, keepAwayTone - requiredDelta); - case NO_PREFERENCE: - final double keepAwayToneStandard = constraint.keepAway.tone.apply(scheme); - final boolean preferLighten = toneStandard > keepAwayToneStandard; - final double alterAmount = Math.abs(delta - requiredDelta); - final boolean lighten = preferLighten ? (tone + alterAmount <= 100.0) : tone < alterAmount; - return lighten ? tone + alterAmount : tone - alterAmount; - } - return tone; - } - - /** - * Given a background tone, find a foreground tone, while ensuring they reach a contrast ratio - * that is as close to ratio as possible. - */ - static double contrastingTone(double bgTone, double ratio) { - final double lighterTone = Contrast.lighterUnsafe(bgTone, ratio); - final double darkerTone = Contrast.darkerUnsafe(bgTone, ratio); - final double lighterRatio = Contrast.ratioOfTones(lighterTone, bgTone); - final double darkerRatio = Contrast.ratioOfTones(darkerTone, bgTone); - final boolean preferLighter = tonePrefersLightForeground(bgTone); - - if (preferLighter) { - // "Neglible difference" handles an edge case where the initial contrast ratio is high - // (ex. 13.0), and the ratio passed to the function is that high ratio, and both the lighter - // and darker ratio fails to pass that ratio. - // - // This was observed with Tonal Spot's On Primary Container turning black momentarily between - // high and max contrast in light mode. PC's standard tone was T90, OPC's was T10, it was - // light mode, and the contrast level was 0.6568521221032331. - final boolean negligibleDifference = - Math.abs(lighterRatio - darkerRatio) < 0.1 && lighterRatio < ratio && darkerRatio < ratio; - if (lighterRatio >= ratio || lighterRatio >= darkerRatio || negligibleDifference) { - return lighterTone; - } else { - return darkerTone; - } - } else { - return darkerRatio >= ratio || darkerRatio >= lighterRatio ? darkerTone : lighterTone; - } - } - - /** - * Adjust a tone down such that white has 4.5 contrast, if the tone is reasonably close to - * supporting it. - */ - static double enableLightForeground(double tone) { - if (tonePrefersLightForeground(tone) && !toneAllowsLightForeground(tone)) { - return 49.0; - } - return tone; - } - - /** - * People prefer white foregrounds on ~T60-70. Observed over time, and also by Andrew Somers - * during research for APCA. - * - *

T60 used as to create the smallest discontinuity possible when skipping down to T49 in order - * to ensure light foregrounds. - */ - static boolean tonePrefersLightForeground(double tone) { - return Math.round(tone) <= 60; - } - - /** Tones less than ~T50 always permit white at 4.5 contrast. */ - static boolean toneAllowsLightForeground(double tone) { - return Math.round(tone) <= 49; - } -} diff --git a/material/java/com/google/android/material/color/utilities/DynamicScheme.java b/material/java/com/google/android/material/color/utilities/DynamicScheme.java deleted file mode 100644 index f9e27ad50..000000000 --- a/material/java/com/google/android/material/color/utilities/DynamicScheme.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * Provides important settings for creating colors dynamically, and 6 color palettes. Requires: 1. A - * color. (source color) 2. A theme. (Variant) 3. Whether or not its dark mode. 4. Contrast level. - * (-1 to 1, currently contrast ratio 3.0 and 7.0) - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class DynamicScheme { - public final int sourceColorArgb; - public final Hct sourceColorHct; - public final Variant variant; - public final boolean isDark; - public final double contrastLevel; - - public final TonalPalette primaryPalette; - public final TonalPalette secondaryPalette; - public final TonalPalette tertiaryPalette; - public final TonalPalette neutralPalette; - public final TonalPalette neutralVariantPalette; - public final TonalPalette errorPalette; - - public DynamicScheme( - Hct sourceColorHct, - Variant variant, - boolean isDark, - double contrastLevel, - TonalPalette primaryPalette, - TonalPalette secondaryPalette, - TonalPalette tertiaryPalette, - TonalPalette neutralPalette, - TonalPalette neutralVariantPalette) { - this.sourceColorArgb = sourceColorHct.toInt(); - this.sourceColorHct = sourceColorHct; - this.variant = variant; - this.isDark = isDark; - this.contrastLevel = contrastLevel; - - this.primaryPalette = primaryPalette; - this.secondaryPalette = secondaryPalette; - this.tertiaryPalette = tertiaryPalette; - this.neutralPalette = neutralPalette; - this.neutralVariantPalette = neutralVariantPalette; - this.errorPalette = TonalPalette.fromHueAndChroma(25.0, 84.0); - } - - public static double getRotatedHue(Hct sourceColorHct, double[] hues, double[] rotations) { - final double sourceHue = sourceColorHct.getHue(); - if (rotations.length == 1) { - return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[0]); - } - final int size = hues.length; - for (int i = 0; i <= (size - 2); i++) { - final double thisHue = hues[i]; - final double nextHue = hues[i + 1]; - if (thisHue < sourceHue && sourceHue < nextHue) { - return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[i]); - } - } - // If this statement executes, something is wrong, there should have been a rotation - // found using the arrays. - return sourceHue; - } -} diff --git a/material/java/com/google/android/material/color/utilities/QuantizerMap.java b/material/java/com/google/android/material/color/utilities/QuantizerMap.java index f1b13b711..665814a38 100644 --- a/material/java/com/google/android/material/color/utilities/QuantizerMap.java +++ b/material/java/com/google/android/material/color/utilities/QuantizerMap.java @@ -19,7 +19,7 @@ import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import androidx.annotation.RestrictTo; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; // TODO(b/254603377): Use copybara to release material color utilities library directly to github. @@ -34,7 +34,7 @@ public final class QuantizerMap implements Quantizer { @Override public QuantizerResult quantize(int[] pixels, int colorCount) { - final Map pixelByCount = new LinkedHashMap<>(); + final HashMap pixelByCount = new HashMap<>(); for (int pixel : pixels) { final Integer currentPixelCount = pixelByCount.get(pixel); final int newPixelCount = currentPixelCount == null ? 1 : currentPixelCount + 1; diff --git a/material/java/com/google/android/material/color/utilities/QuantizerWsmeans.java b/material/java/com/google/android/material/color/utilities/QuantizerWsmeans.java index 152b62c7d..4c535b768 100644 --- a/material/java/com/google/android/material/color/utilities/QuantizerWsmeans.java +++ b/material/java/com/google/android/material/color/utilities/QuantizerWsmeans.java @@ -21,9 +21,8 @@ import androidx.annotation.RestrictTo; import java.util.Arrays; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; -import java.util.Random; // TODO(b/254603377): Use copybara to release material color utilities library directly to github. /** @@ -76,10 +75,7 @@ public int compareTo(Distance other) { */ public static Map quantize( int[] inputPixels, int[] startingClusters, int maxColors) { - // Uses a seeded random number generator to ensure consistent results. - Random random = new Random(0x42688); - - Map pixelToCount = new LinkedHashMap<>(); + Map pixelToCount = new HashMap<>(); double[][] points = new double[inputPixels.length][]; int[] pixels = new int[inputPixels.length]; PointProvider pointProvider = new PointProviderLab(); @@ -125,7 +121,7 @@ public static Map quantize( int[] clusterIndices = new int[pointCount]; for (int i = 0; i < pointCount; i++) { - clusterIndices[i] = random.nextInt(clusterCount); + clusterIndices[i] = (int) Math.floor(Math.random() * clusterCount); } int[][] indexMatrix = new int[clusterCount][]; @@ -219,7 +215,7 @@ public static Map quantize( } } - Map argbToPopulation = new LinkedHashMap<>(); + Map argbToPopulation = new HashMap<>(); for (int i = 0; i < clusterCount; i++) { int count = pixelCountSums[i]; if (count == 0) { diff --git a/material/java/com/google/android/material/color/utilities/QuantizerWu.java b/material/java/com/google/android/material/color/utilities/QuantizerWu.java index 6f13688f7..312c60e56 100644 --- a/material/java/com/google/android/material/color/utilities/QuantizerWu.java +++ b/material/java/com/google/android/material/color/utilities/QuantizerWu.java @@ -20,7 +20,7 @@ import androidx.annotation.RestrictTo; import java.util.ArrayList; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,7 +57,7 @@ public QuantizerResult quantize(int[] pixels, int colorCount) { createMoments(); CreateBoxesResult createBoxesResult = createBoxes(colorCount); List colors = createResult(createBoxesResult.resultCount); - Map resultMap = new LinkedHashMap<>(); + HashMap resultMap = new HashMap<>(); for (int color : colors) { resultMap.put(color, 0); } diff --git a/material/java/com/google/android/material/color/utilities/SchemeExpressive.java b/material/java/com/google/android/material/color/utilities/SchemeExpressive.java deleted file mode 100644 index ea56bccd1..000000000 --- a/material/java/com/google/android/material/color/utilities/SchemeExpressive.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * A playful theme - the source color's hue does not appear in the theme. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SchemeExpressive extends DynamicScheme { - // NOMUTANTS--arbitrary increments/decrements, correctly, still passes tests. - private static final double[] HUES = {0, 21, 51, 121, 151, 191, 271, 321, 360}; - private static final double[] SECONDARY_ROTATIONS = {45, 95, 45, 20, 45, 90, 45, 45, 45}; - private static final double[] TERTIARY_ROTATIONS = {120, 120, 20, 45, 20, 15, 20, 120, 120}; - - public SchemeExpressive(Hct sourceColorHct, boolean isDark, double contrastLevel) { - super( - sourceColorHct, - Variant.EXPRESSIVE, - isDark, - contrastLevel, - TonalPalette.fromHueAndChroma( - MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 120.0), 40.0), - TonalPalette.fromHueAndChroma( - DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS), 24.0), - TonalPalette.fromHueAndChroma( - DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS), 32.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0)); - } -} diff --git a/material/java/com/google/android/material/color/utilities/SchemeMonochrome.java b/material/java/com/google/android/material/color/utilities/SchemeMonochrome.java deleted file mode 100644 index 3ada98d2f..000000000 --- a/material/java/com/google/android/material/color/utilities/SchemeMonochrome.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * A monochrome theme, colors are purely black / white / gray. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SchemeMonochrome extends DynamicScheme { - public SchemeMonochrome(Hct sourceColorHct, boolean isDark, double contrastLevel) { - super( - sourceColorHct, - Variant.MONOCHROME, - isDark, - contrastLevel, - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0)); - } -} diff --git a/material/java/com/google/android/material/color/utilities/SchemeNeutral.java b/material/java/com/google/android/material/color/utilities/SchemeNeutral.java deleted file mode 100644 index 67e0fa9ca..000000000 --- a/material/java/com/google/android/material/color/utilities/SchemeNeutral.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * A theme that's slightly more chromatic than monochrome, which is purely black / white / gray. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SchemeNeutral extends DynamicScheme { - public SchemeNeutral(Hct sourceColorHct, boolean isDark, double contrastLevel) { - super( - sourceColorHct, - Variant.NEUTRAL, - isDark, - contrastLevel, - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0)); - } -} diff --git a/material/java/com/google/android/material/color/utilities/SchemeTonalSpot.java b/material/java/com/google/android/material/color/utilities/SchemeTonalSpot.java deleted file mode 100644 index ec0735c84..000000000 --- a/material/java/com/google/android/material/color/utilities/SchemeTonalSpot.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * A calm theme, sedated colors that aren't particularly chromatic. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SchemeTonalSpot extends DynamicScheme { - public SchemeTonalSpot(Hct sourceColorHct, boolean isDark, double contrastLevel) { - super( - sourceColorHct, - Variant.TONAL_SPOT, - isDark, - contrastLevel, - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 40.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0), - TonalPalette.fromHueAndChroma( - MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 6.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0)); - } -} diff --git a/material/java/com/google/android/material/color/utilities/SchemeVibrant.java b/material/java/com/google/android/material/color/utilities/SchemeVibrant.java deleted file mode 100644 index 818a2fc4f..000000000 --- a/material/java/com/google/android/material/color/utilities/SchemeVibrant.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * A loud theme, colorfulness is maximum for Primary palette, increased for others. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public class SchemeVibrant extends DynamicScheme { - private static final double[] HUES = {0, 41, 61, 101, 131, 181, 251, 301, 360}; - private static final double[] SECONDARY_ROTATIONS = {18, 15, 10, 12, 15, 18, 15, 12, 12}; - private static final double[] TERTIARY_ROTATIONS = {35, 30, 20, 25, 30, 35, 30, 25, 25}; - - public SchemeVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) { - super( - sourceColorHct, - Variant.VIBRANT, - isDark, - contrastLevel, - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 200.0), - TonalPalette.fromHueAndChroma( - DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS), 24.0), - TonalPalette.fromHueAndChroma( - DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS), 32.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0), - TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0)); - } -} diff --git a/material/java/com/google/android/material/color/utilities/TonalPalette.java b/material/java/com/google/android/material/color/utilities/TonalPalette.java index a753a95ae..ea869cf09 100644 --- a/material/java/com/google/android/material/color/utilities/TonalPalette.java +++ b/material/java/com/google/android/material/color/utilities/TonalPalette.java @@ -78,12 +78,4 @@ public int tone(int tone) { } return color; } - - public double getChroma() { - return this.chroma; - } - - public double getHue() { - return this.hue; - } } diff --git a/material/java/com/google/android/material/color/utilities/ToneDeltaConstraint.java b/material/java/com/google/android/material/color/utilities/ToneDeltaConstraint.java deleted file mode 100644 index 391ff55d8..000000000 --- a/material/java/com/google/android/material/color/utilities/ToneDeltaConstraint.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * Documents a constraint between two DynamicColors, in which their tones must have a certain - * distance from each other. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public final class ToneDeltaConstraint { - public final double delta; - public final DynamicColor keepAway; - public final TonePolarity keepAwayPolarity; - - /** - * @param delta the difference in tone required - * @param keepAway the color to distance in tone from - * @param keepAwayPolarity whether the color to keep away from must be lighter, darker, or no - * preference, in which case it should - */ - public ToneDeltaConstraint(double delta, DynamicColor keepAway, TonePolarity keepAwayPolarity) { - this.delta = delta; - this.keepAway = keepAway; - this.keepAwayPolarity = keepAwayPolarity; - } -} diff --git a/material/java/com/google/android/material/color/utilities/Variant.java b/material/java/com/google/android/material/color/utilities/Variant.java deleted file mode 100644 index 24f2f3a00..000000000 --- a/material/java/com/google/android/material/color/utilities/Variant.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.material.color.utilities; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; - -import androidx.annotation.RestrictTo; - -/** - * Themes for Dynamic Color. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public enum Variant { - MONOCHROME, - NEUTRAL, - TONAL_SPOT, - VIBRANT, - EXPRESSIVE; -} diff --git a/material/java/com/google/android/material/datepicker/res/values/styles.xml b/material/java/com/google/android/material/datepicker/res/values/styles.xml index a55d37755..1a9ee11d8 100644 --- a/material/java/com/google/android/material/datepicker/res/values/styles.xml +++ b/material/java/com/google/android/material/datepicker/res/values/styles.xml @@ -234,7 +234,7 @@ - - - 0 + rounded + - - - - - 0 + rounded + + - - 0 + rounded - - 0 + rounded - - - 0 + rounded + + rounded - + rounded diff --git a/material/java/com/google/android/material/sidesheet/RightSheetDelegate.java b/material/java/com/google/android/material/sidesheet/RightSheetDelegate.java index bcc514e21..8cfee7b2c 100644 --- a/material/java/com/google/android/material/sidesheet/RightSheetDelegate.java +++ b/material/java/com/google/android/material/sidesheet/RightSheetDelegate.java @@ -16,15 +16,13 @@ package com.google.android.material.sidesheet; -import static com.google.android.material.sidesheet.Sheet.STATE_DRAGGING; import static com.google.android.material.sidesheet.Sheet.STATE_EXPANDED; import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN; import static java.lang.Math.max; import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.view.ViewCompat; import androidx.customview.widget.ViewDragHelper; import com.google.android.material.sidesheet.Sheet.SheetEdge; import com.google.android.material.sidesheet.Sheet.StableSheetState; @@ -44,7 +42,7 @@ final class RightSheetDelegate extends SheetDelegate { @SheetEdge @Override int getSheetEdge() { - return SideSheetBehavior.RIGHT; + return SideSheetBehavior.EDGE_RIGHT; } /** Returns the sheet's offset in pixels from the origin edge when hidden. */ @@ -105,79 +103,6 @@ private boolean isSwipeSignificant(float xVelocity, float yVelocity) { && yVelocity > sheetBehavior.getSignificantVelocityThreshold(); } - @Override - void setTargetStateOnNestedPreScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - int dx, - int dy, - @NonNull int[] consumed, - int type) { - int currentLeft = child.getLeft(); - int newLeft = currentLeft - dx; - if (dx < 0) { // Moving towards the left. - if (newLeft > getExpandedOffset()) { - consumed[1] = currentLeft - getExpandedOffset(); - ViewCompat.offsetLeftAndRight(child, -consumed[1]); - sheetBehavior.setStateInternal(STATE_EXPANDED); - } else { - if (!sheetBehavior.isDraggable()) { - // Prevent dragging - return; - } - - consumed[1] = dx; - ViewCompat.offsetLeftAndRight(child, -dx); - sheetBehavior.setStateInternal(STATE_DRAGGING); - } - } else if (dx > 0) { // Moving towards the right. - if (!target.canScrollHorizontally(-1)) { - if (newLeft <= getHiddenOffset()) { - if (!sheetBehavior.isDraggable()) { - // Prevent dragging - return; - } - - consumed[1] = dx; - ViewCompat.offsetLeftAndRight(child, dx); - sheetBehavior.setStateInternal(STATE_DRAGGING); - } else { - consumed[1] = currentLeft - getHiddenOffset(); - ViewCompat.offsetLeftAndRight(child, consumed[1]); - sheetBehavior.setStateInternal(STATE_HIDDEN); - } - } - } - } - - @Override - @StableSheetState - int calculateTargetStateOnStopNestedScroll(@NonNull V child) { - @StableSheetState int targetState; - if (sheetBehavior.getLastNestedScrollDx() > 0) { - targetState = STATE_EXPANDED; - } else if (sheetBehavior.shouldHide(child, sheetBehavior.getXVelocity())) { - targetState = STATE_HIDDEN; - } else if (sheetBehavior.getLastNestedScrollDx() == 0) { - int currentLeft = child.getLeft(); - - if (Math.abs(currentLeft - getExpandedOffset()) < Math.abs(currentLeft - getHiddenOffset())) { - targetState = STATE_EXPANDED; - } else { - targetState = STATE_HIDDEN; - } - } else { - targetState = STATE_HIDDEN; - } - return targetState; - } - - @Override - boolean hasReachedExpandedOffset(@NonNull V child) { - return child.getLeft() == getExpandedOffset(); - } - @Override boolean shouldHide(@NonNull View child, float velocity) { final float newRight = child.getRight() + velocity * sheetBehavior.getHideFriction(); @@ -198,4 +123,24 @@ boolean isSettling(View child, int state, boolean isReleasingView) { int getOutwardEdge(@NonNull V child) { return child.getLeft(); } + + @Override + float calculateSlideOffsetBasedOnOutwardEdge(int left) { + float hiddenOffset = getHiddenOffset(); + float sheetWidth = hiddenOffset - getExpandedOffset(); + + return (hiddenOffset - left) / sheetWidth; + } + + @Override + void updateCoplanarSiblingLayoutParams( + @NonNull MarginLayoutParams coplanarSiblingLayoutParams, int sheetLeft, int sheetRight) { + int parentWidth = sheetBehavior.getParentWidth(); + + // Wait until the sheet partially enters the screen to avoid an initial content jump to the + // right edge of the screen. + if (sheetLeft <= parentWidth) { + coplanarSiblingLayoutParams.rightMargin = parentWidth - sheetLeft; + } + } } diff --git a/material/java/com/google/android/material/sidesheet/Sheet.java b/material/java/com/google/android/material/sidesheet/Sheet.java index 64faf3b4d..7807fa85a 100644 --- a/material/java/com/google/android/material/sidesheet/Sheet.java +++ b/material/java/com/google/android/material/sidesheet/Sheet.java @@ -27,7 +27,7 @@ * Interface for sheet constants and {@code IntDefs} to be shared between the different {@link * Sheet} implementations. */ -interface Sheet { +interface Sheet { /** The sheet is dragging. */ int STATE_DRAGGING = 1; @@ -66,8 +66,11 @@ interface Sheet { @Retention(RetentionPolicy.SOURCE) @interface StableSheetState {} - /** The sheet is based on the right edge; it slides from the right edge towards the left. */ - int RIGHT = 0; + /** + * The sheet is based on the right edge of the screen; it slides from the right edge towards the + * left. + */ + int EDGE_RIGHT = 0; /** * The edge of the screen that a sheet slides out of. @@ -75,7 +78,7 @@ interface Sheet { * @hide */ @RestrictTo(LIBRARY_GROUP) - @IntDef({RIGHT}) + @IntDef({EDGE_RIGHT}) @Retention(RetentionPolicy.SOURCE) @interface SheetEdge {} @@ -89,4 +92,18 @@ interface Sheet { /** Sets the current state of the sheet. Must be one of {@link StableSheetState}. */ void setState(@StableSheetState int state); + + /** + * Adds a callback to be notified of sheet events. + * + * @param callback The callback to notify when sheet events occur. + */ + void addCallback(C callback); + + /** + * Removes a callback to be notified of sheet events. + * + * @param callback The callback to remove + */ + void removeCallback(C callback); } diff --git a/material/java/com/google/android/material/color/utilities/TonePolarity.java b/material/java/com/google/android/material/sidesheet/SheetCallback.java similarity index 50% rename from material/java/com/google/android/material/color/utilities/TonePolarity.java rename to material/java/com/google/android/material/sidesheet/SheetCallback.java index d991d545c..e6b13909c 100644 --- a/material/java/com/google/android/material/color/utilities/TonePolarity.java +++ b/material/java/com/google/android/material/sidesheet/SheetCallback.java @@ -14,20 +14,27 @@ * limitations under the License. */ -package com.google.android.material.color.utilities; +package com.google.android.material.sidesheet; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import android.view.View; +import androidx.annotation.NonNull; +import com.google.android.material.sidesheet.Sheet.SheetState; -import androidx.annotation.RestrictTo; +interface SheetCallback { -/** - * Describes the relationship in lightness between two colors. - * - * @hide - */ -@RestrictTo(LIBRARY_GROUP) -public enum TonePolarity { - DARKER, - LIGHTER, - NO_PREFERENCE; + /** + * Called when the sheet changes its state. + * + * @param sheet The sheet view. + * @param newState The new state. + */ + void onStateChanged(@NonNull View sheet, @SheetState int newState); + + /** + * Called when the sheet is being dragged. + * + * @param sheet The sheet view. + * @param slideOffset The new offset of this sheet. + */ + void onSlide(@NonNull View sheet, float slideOffset); } diff --git a/material/java/com/google/android/material/sidesheet/SheetDelegate.java b/material/java/com/google/android/material/sidesheet/SheetDelegate.java index 05b164fa8..d4caf46b1 100644 --- a/material/java/com/google/android/material/sidesheet/SheetDelegate.java +++ b/material/java/com/google/android/material/sidesheet/SheetDelegate.java @@ -17,8 +17,8 @@ package com.google.android.material.sidesheet; import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.material.sidesheet.Sheet.SheetEdge; import com.google.android.material.sidesheet.Sheet.StableSheetState; import com.google.android.material.sidesheet.SideSheetBehavior.StateSettlingTracker; @@ -57,30 +57,6 @@ abstract class SheetDelegate { abstract int calculateTargetStateOnViewReleased( @NonNull View releasedChild, float xVelocity, float yVelocity); - /** - * Sets the target sheet state from the {@link - * SideSheetBehavior#onNestedPreScroll(CoordinatorLayout, View, View, int, int, int[], int)} - * callback, based on scroll and position information provided by the method parameters. - */ - abstract void setTargetStateOnNestedPreScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - int dx, - int dy, - @NonNull int[] consumed, - int type); - - @StableSheetState - abstract int calculateTargetStateOnStopNestedScroll(@NonNull V child); - - /** - * Whether the sheet has reached the expanded offset at the end of a nested scroll, used to - * determine when to set the {@link SideSheetBehavior.SheetState} to {@link - * SideSheetBehavior.SheetState#STATE_EXPANDED}. - */ - abstract boolean hasReachedExpandedOffset(@NonNull V child); - /** * Whether the sheet should hide, based on the position of child, velocity of the drag event, and * {@link SideSheetBehavior#getHideThreshold()}. @@ -93,4 +69,17 @@ abstract void setTargetStateOnNestedPreScroll( * this would return {@code child.getLeft()}. */ abstract int getOutwardEdge(@NonNull V child); + + /** + * Returns the calculated slide offset based on which edge of the screen the sheet is based on. + * The offset value increases as the sheet moves towards the outward edge. + * + * @return slide offset represented as a float value between 0 and 1. A value of 0 means that the + * sheet is hidden and a value of 1 means that the sheet is fully expanded. + */ + abstract float calculateSlideOffsetBasedOnOutwardEdge(int outwardEdge); + + /** Set the coplanar sheet layout params depending on the screen size. */ + abstract void updateCoplanarSiblingLayoutParams( + @NonNull MarginLayoutParams coplanarSiblingLayoutParams, int sheetLeft, int sheetRight); } diff --git a/material/java/com/google/android/material/sidesheet/SheetDialog.java b/material/java/com/google/android/material/sidesheet/SheetDialog.java index f0eaefe4e..84d522ddb 100644 --- a/material/java/com/google/android/material/sidesheet/SheetDialog.java +++ b/material/java/com/google/android/material/sidesheet/SheetDialog.java @@ -18,7 +18,6 @@ import com.google.android.material.R; - import android.content.Context; import android.content.res.TypedArray; import android.os.Build.VERSION; @@ -47,12 +46,12 @@ * Base class for {@link android.app.Dialog}s styled as a sheet, to be used by sheet dialog * implementations such as side sheets and bottom sheets. */ -abstract class SheetDialog extends AppCompatDialog { +abstract class SheetDialog extends AppCompatDialog { private static final int COORDINATOR_LAYOUT_ID = R.id.coordinator; private static final int TOUCH_OUTSIDE_ID = R.id.touch_outside; - @Nullable private Sheet behavior; + @Nullable private Sheet behavior; @Nullable private FrameLayout container; @Nullable private FrameLayout sheet; @@ -134,15 +133,15 @@ protected void onStart() { * or calling `dismiss()` from a `SideSheetDialogFragment`, tapping outside a dialog, etc... * *

The default animation to dismiss this dialog is a fade-out transition through a - * windowAnimation. Set {@link #setDismissWithAnimation(boolean)} to true if you want to utilize - * the sheet animation instead. + * windowAnimation. Set {@link #setDismissWithSheetAnimationEnabled(boolean)} to true if you want + * to utilize the sheet animation instead. * *

If this function is called from a swipe interaction, or dismissWithAnimation is false, then * keep the default behavior. */ @Override public void cancel() { - Sheet behavior = getBehavior(); + Sheet behavior = getBehavior(); if (!dismissWithAnimation || behavior.getState() == Sheet.STATE_HIDDEN) { super.cancel(); @@ -162,20 +161,20 @@ public void setCanceledOnTouchOutside(boolean cancel) { } /** - * Set to perform the swipe away animation when dismissing instead of the window animation for the - * dialog. + * Set whether to perform the swipe away animation on the sheet when dismissing, rather than the + * window animation for the dialog. * * @param dismissWithAnimation True if swipe away animation should be used when dismissing. */ - public void setDismissWithAnimation(boolean dismissWithAnimation) { + public void setDismissWithSheetAnimationEnabled(boolean dismissWithAnimation) { this.dismissWithAnimation = dismissWithAnimation; } /** - * Returns if dismissing will perform the swipe away animation on the sheet, rather than the + * Returns whether dismissing will perform the swipe away animation on the sheet, rather than the * window animation for the dialog. */ - public boolean getDismissWithAnimation() { + public boolean isDismissWithSheetAnimationEnabled() { return dismissWithAnimation; } @@ -185,9 +184,12 @@ private void ensureContainerAndBehavior() { container = (FrameLayout) View.inflate(getContext(), getLayoutResId(), null); sheet = container.findViewById(getDialogId()); behavior = getBehaviorFromSheet(sheet); + addSheetCancelOnHideCallback(behavior); } } + abstract void addSheetCancelOnHideCallback(Sheet behavior); + @NonNull private FrameLayout getContainer() { if (this.container == null) { @@ -205,7 +207,7 @@ private FrameLayout getSheet() { } @NonNull - Sheet getBehavior() { + Sheet getBehavior() { if (this.behavior == null) { // The content hasn't been set, so the behavior doesn't exist yet. Let's create it. ensureContainerAndBehavior(); @@ -267,7 +269,7 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) { return container; } - boolean shouldWindowCloseOnTouchOutside() { + private boolean shouldWindowCloseOnTouchOutside() { if (!canceledOnTouchOutsideSet) { TypedArray a = getContext().obtainStyledAttributes(new int[] {android.R.attr.windowCloseOnTouchOutside}); @@ -297,14 +299,14 @@ private static int getThemeResId( } @LayoutRes - protected abstract int getLayoutResId(); + abstract int getLayoutResId(); @IdRes - protected abstract int getDialogId(); + abstract int getDialogId(); @NonNull - protected abstract Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet); + abstract Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet); @StableSheetState - protected abstract int getStateOnStart(); + abstract int getStateOnStart(); } diff --git a/material/java/com/google/android/material/sidesheet/SideSheetBehavior.java b/material/java/com/google/android/material/sidesheet/SideSheetBehavior.java index b4ad42833..57e5b326c 100644 --- a/material/java/com/google/android/material/sidesheet/SideSheetBehavior.java +++ b/material/java/com/google/android/material/sidesheet/SideSheetBehavior.java @@ -24,8 +24,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -39,10 +37,10 @@ import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; -import androidx.annotation.VisibleForTesting; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams; import androidx.core.math.MathUtils; @@ -52,19 +50,20 @@ import androidx.core.view.accessibility.AccessibilityViewCommand; import androidx.customview.view.AbsSavedState; import androidx.customview.widget.ViewDragHelper; +import com.google.android.material.internal.ViewUtils; import com.google.android.material.resources.MaterialResources; import com.google.android.material.shape.MaterialShapeDrawable; import com.google.android.material.shape.ShapeAppearanceModel; import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; /** * An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as a * side sheet. */ public class SideSheetBehavior extends CoordinatorLayout.Behavior - implements Sheet { + implements Sheet { private SheetDelegate sheetDelegate; @@ -76,8 +75,6 @@ public class SideSheetBehavior extends CoordinatorLayout.Behavio private static final int NO_MAX_SIZE = -1; - private static final boolean UPDATE_IMPORTANT_FOR_ACCESSIBILITY_ON_SIBLINGS = false; - private float maximumVelocity; @Nullable private MaterialShapeDrawable materialShapeDrawable; @@ -103,29 +100,20 @@ public class SideSheetBehavior extends CoordinatorLayout.Behavio private boolean ignoreEvents; - private int lastNestedScrollDx; - - private boolean nestedScrolled; - private float hideFriction = HIDE_FRICTION; private int childWidth; private int parentWidth; @Nullable private WeakReference viewRef; - - @Nullable private WeakReference nestedScrollingChildRef; + @Nullable private WeakReference coplanarSiblingViewRef; + @IdRes private int coplanarSiblingViewId = View.NO_ID; @Nullable private VelocityTracker velocityTracker; - private int activePointerId; - private int initialX; - private int initialY; - - private boolean touchingScrollingChild; - @Nullable private Map importantForAccessibilityMap; + @NonNull private final Set callbacks = new LinkedHashSet<>(); public SideSheetBehavior() {} @@ -142,6 +130,10 @@ public SideSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs) this.shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, 0, DEF_STYLE_RES).build(); } + if (a.hasValue(R.styleable.SideSheetBehavior_Layout_coplanarSiblingViewId)) { + setCoplanarSiblingViewId( + a.getResourceId(R.styleable.SideSheetBehavior_Layout_coplanarSiblingViewId, View.NO_ID)); + } createMaterialShapeDrawableIfNeeded(context); this.elevation = a.getDimension(R.styleable.SideSheetBehavior_Layout_android_elevation, -1); @@ -158,19 +150,19 @@ public SideSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs) private void setSheetEdge(@SheetEdge int sheetEdge) { if (sheetDelegate == null || sheetDelegate.getSheetEdge() != sheetEdge) { - if (sheetEdge == RIGHT) { + if (sheetEdge == EDGE_RIGHT) { this.sheetDelegate = new RightSheetDelegate(this); return; } throw new IllegalArgumentException( - "Invalid sheet edge position value: " + sheetEdge + ". Must be " + RIGHT); + "Invalid sheet edge position value: " + sheetEdge + ". Must be " + EDGE_RIGHT); } } @SheetEdge private int getDefaultSheetEdge() { - return RIGHT; + return EDGE_RIGHT; } /** @@ -318,10 +310,26 @@ public boolean onLayoutChild( ViewCompat.offsetLeftAndRight(child, currentOffset); - nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child)); + maybeAssignCoplanarSiblingViewBasedId(parent); + + for (SheetCallback callback : callbacks) { + if (callback instanceof SideSheetCallback) { + SideSheetCallback sideSheetCallback = (SideSheetCallback) callback; + sideSheetCallback.onLayout(child); + } + } return true; } + private void maybeAssignCoplanarSiblingViewBasedId(@NonNull CoordinatorLayout parent) { + if (coplanarSiblingViewRef == null && coplanarSiblingViewId != View.NO_ID) { + View coplanarSiblingView = parent.findViewById(coplanarSiblingViewId); + if (coplanarSiblingView != null) { + this.coplanarSiblingViewRef = new WeakReference<>(coplanarSiblingView); + } + } + } + int getChildWidth() { return childWidth; } @@ -335,7 +343,11 @@ private int calculateCurrentOffset(int savedOutwardEdge, V child) { switch (state) { case STATE_EXPANDED: - currentOffset = savedOutwardEdge; + // TODO (b/261619910): This is a workaround for a bug where the expanded offset was getting + // recalculated if onLayoutChild() was called while the sheet was in the process of + // expanding/offsetting. Revisit this and refactor if necessary when adding left based + // sheets. + currentOffset = ViewUtils.isLayoutRtl(child) ? getExpandedOffset() : 0; break; case STATE_DRAGGING: case STATE_SETTLING: @@ -369,8 +381,6 @@ public boolean onInterceptTouchEvent( switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - touchingScrollingChild = false; - activePointerId = MotionEvent.INVALID_POINTER_ID; // Reset the ignore flag if (ignoreEvents) { ignoreEvents = false; @@ -379,40 +389,12 @@ public boolean onInterceptTouchEvent( break; case MotionEvent.ACTION_DOWN: initialX = (int) event.getX(); - initialY = (int) event.getY(); - // Only intercept nested scrolling events here if the view is not being moved by the - // ViewDragHelper. - if (state != STATE_SETTLING) { - View nestedScrollChild = - nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; - if (nestedScrollChild != null - && parent.isPointInChildBounds(nestedScrollChild, initialX, initialY)) { - activePointerId = event.getPointerId(event.getActionIndex()); - touchingScrollingChild = true; - } - } - ignoreEvents = - activePointerId == MotionEvent.INVALID_POINTER_ID - && !parent.isPointInChildBounds(child, initialX, initialY); break; default: // fall out } - if (!ignoreEvents - && viewDragHelper != null - && viewDragHelper.shouldInterceptTouchEvent(event)) { - return true; - } - // We have to handle cases where the ViewDragHelper does not capture the sheet because - // it is not the top most view of its parent. This is not necessary when the touch event is - // happening over the scrolling content as nested scrolling logic handles that case. - View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; - return action == MotionEvent.ACTION_MOVE - && scroll != null - && !ignoreEvents - && state != STATE_DRAGGING - && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) + return !ignoreEvents && viewDragHelper != null - && calculateDragDistance(initialY, event.getY()) > viewDragHelper.getTouchSlop(); + && viewDragHelper.shouldInterceptTouchEvent(event); } int getSignificantVelocityThreshold() { @@ -462,98 +444,6 @@ private float calculateDragDistance(float initialPoint, float currentPoint) { return Math.abs(initialPoint - currentPoint); } - @Override - public boolean onStartNestedScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View directTargetChild, - @NonNull View target, - int axes, - int type) { - lastNestedScrollDx = 0; - nestedScrolled = false; - return (axes & ViewCompat.SCROLL_AXIS_HORIZONTAL) != 0; - } - - @Override - public void onNestedPreScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - int dx, - int dy, - @NonNull int[] consumed, - int type) { - if (type == ViewCompat.TYPE_NON_TOUCH) { - // Ignore fling here. The ViewDragHelper handles it. - return; - } - View scrollingChild = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; - if (isNestedScrollingCheckEnabled() && target != scrollingChild) { - return; - } - sheetDelegate.setTargetStateOnNestedPreScroll( - coordinatorLayout, child, target, dx, dy, consumed, type); - lastNestedScrollDx = dx; - nestedScrolled = true; - } - - @Override - public void onStopNestedScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - int type) { - if (sheetDelegate.hasReachedExpandedOffset(child)) { - setStateInternal(STATE_EXPANDED); - return; - } - if (isNestedScrollingCheckEnabled() - && (nestedScrollingChildRef == null - || target != nestedScrollingChildRef.get() - || !nestedScrolled)) { - return; - } - @StableSheetState int targetState = sheetDelegate.calculateTargetStateOnStopNestedScroll(child); - startSettling(child, targetState, false); - nestedScrolled = false; - } - - @Override - public void onNestedScroll( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - int dxConsumed, - int dyConsumed, - int dxUnconsumed, - int dyUnconsumed, - int type, - @NonNull int[] consumed) { - // Overridden to prevent the default consumption of the entire scroll distance. - } - - @Override - public boolean onNestedPreFling( - @NonNull CoordinatorLayout coordinatorLayout, - @NonNull V child, - @NonNull View target, - float velocityX, - float velocityY) { - - if (nestedScrollingChildRef == null || !isNestedScrollingCheckEnabled()) { - return false; - } - - return target == nestedScrollingChildRef.get() - && (state != STATE_EXPANDED - || super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)); - } - - int getLastNestedScrollDx() { - return lastNestedScrollDx; - } - /** * Returns the sheet's offset from the origin edge when expanded. It will calculate the offset * based on the width of the content. @@ -601,6 +491,26 @@ float getHideThreshold() { return HIDE_THRESHOLD; } + /** + * Adds a callback to be notified of side sheet events. + * + * @param callback The callback to notify when side sheet events occur. + */ + @Override + public void addCallback(@NonNull SideSheetCallback callback) { + callbacks.add(callback); + } + + /** + * Removes a previously added callback. + * + * @param callback The callback to remove. + */ + @Override + public void removeCallback(@NonNull SideSheetCallback callback) { + callbacks.remove(callback); + } + /** * Sets the state of the sheet. The sheet will transition to that state with animation. * @@ -674,16 +584,17 @@ void setStateInternal(@SheetState int state) { } if (state == STATE_EXPANDED) { - updateImportantForAccessibility(true); - } else if (state == STATE_HIDDEN) { - updateImportantForAccessibility(false); + updateAccessibilityFocusOnExpansion(); + } + + for (SheetCallback callback : callbacks) { + callback.onStateChanged(sheet, state); } updateAccessibilityActions(); } private void resetVelocity() { - activePointerId = ViewDragHelper.INVALID_POINTER; if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; @@ -694,27 +605,6 @@ boolean shouldHide(@NonNull View child, float velocity) { return sheetDelegate.shouldHide(child, velocity); } - @Nullable - @VisibleForTesting - View findScrollingChild(View view) { - if (view.getVisibility() != View.VISIBLE) { - return null; - } - if (ViewCompat.isNestedScrollingEnabled(view)) { - return view; - } - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - for (int i = 0, count = group.getChildCount(); i < count; i++) { - View scrollingChild = findScrollingChild(group.getChildAt(i)); - if (scrollingChild != null) { - return scrollingChild; - } - } - } - return null; - } - private boolean shouldHandleDraggingWithHelper() { // If it's not draggable, do not forward events to viewDragHelper; however, if it's already // dragging, let it finish. @@ -744,7 +634,7 @@ float getXVelocity() { return 0; } velocityTracker.computeCurrentVelocity(1000, maximumVelocity); - return velocityTracker.getXVelocity(activePointerId); + return velocityTracker.getXVelocity(); } private void startSettling(View child, @StableSheetState int state, boolean isReleasingView) { @@ -781,17 +671,24 @@ public boolean tryCaptureView(@NonNull View child, int pointerId) { if (state == STATE_DRAGGING) { return false; } - if (touchingScrollingChild) { - return false; - } - if (state == STATE_EXPANDED && activePointerId == pointerId) { - View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; - if (scroll != null && scroll.canScrollVertically(-1)) { - // Let the content scroll. - return false; + return viewRef != null && viewRef.get() == child; + } + + @Override + public void onViewPositionChanged( + @NonNull View changedView, int left, int top, int dx, int dy) { + View coplanarSiblingView = getCoplanarSiblingView(); + if (coplanarSiblingView != null) { + MarginLayoutParams layoutParams = + (MarginLayoutParams) coplanarSiblingView.getLayoutParams(); + if (layoutParams != null) { + sheetDelegate.updateCoplanarSiblingLayoutParams( + layoutParams, changedView.getLeft(), changedView.getRight()); + coplanarSiblingView.setLayoutParams(layoutParams); } } - return viewRef != null && viewRef.get() == child; + + dispatchOnSlide(changedView, left); } @Override @@ -825,15 +722,70 @@ public int getViewHorizontalDragRange(@NonNull View child) { } }; + private void dispatchOnSlide(@NonNull View child, int outwardEdge) { + if (!callbacks.isEmpty()) { + float slideOffset = sheetDelegate.calculateSlideOffsetBasedOnOutwardEdge(outwardEdge); + for (SheetCallback callback : callbacks) { + callback.onSlide(child, slideOffset); + } + } + } + /** - * Checks whether a nested scroll should be enabled. If {@code false} all nested scrolls will be - * consumed by the side sheet. + * Set the sibling id to use for coplanar sheet expansion. If a coplanar sibling has previously + * been set either by this method or via {@link #setCoplanarSiblingView(View)}, that View + * reference will be cleared in favor of this new coplanar sibling reference. * - * @hide + * @param coplanarSiblingViewId the id of the coplanar sibling */ - @RestrictTo(LIBRARY_GROUP) - public boolean isNestedScrollingCheckEnabled() { - return true; + public void setCoplanarSiblingViewId(@IdRes int coplanarSiblingViewId) { + this.coplanarSiblingViewId = coplanarSiblingViewId; + // Clear any potential coplanar sibling view to make sure that we use this view id rather than + // an existing coplanar sibling view. + clearCoplanarSiblingView(); + // Request layout to find the view and trigger a layout pass. + if (viewRef != null) { + View view = viewRef.get(); + if (coplanarSiblingViewId != View.NO_ID && ViewCompat.isLaidOut(view)) { + view.requestLayout(); + } + } + } + + /** + * Set the sibling view to use for coplanar sheet expansion. If a coplanar sibling has previously + * been set either by this method or via {@link #setCoplanarSiblingViewId(int)}, that reference + * will be cleared in favor of this new coplanar sibling reference. + * + * @param coplanarSiblingView the sibling view to squash during coplanar expansion + */ + public void setCoplanarSiblingView(@Nullable View coplanarSiblingView) { + this.coplanarSiblingViewId = View.NO_ID; + if (coplanarSiblingView == null) { + clearCoplanarSiblingView(); + } else { + this.coplanarSiblingViewRef = new WeakReference<>(coplanarSiblingView); + // Request layout to make the new view take effect. + if (viewRef != null) { + View view = viewRef.get(); + if (ViewCompat.isLaidOut(view)) { + view.requestLayout(); + } + } + } + } + + /** Returns the sibling view that is used for coplanar sheet expansion. */ + @Nullable + public View getCoplanarSiblingView() { + return coplanarSiblingViewRef != null ? coplanarSiblingViewRef.get() : null; + } + + private void clearCoplanarSiblingView() { + if (this.coplanarSiblingViewRef != null) { + this.coplanarSiblingViewRef.clear(); + } + this.coplanarSiblingViewRef = null; } /** @@ -952,53 +904,19 @@ public static SideSheetBehavior from(@NonNull V view) { return (SideSheetBehavior) behavior; } - private void updateImportantForAccessibility(boolean expanded) { + private void updateAccessibilityFocusOnExpansion() { if (viewRef == null) { return; } - - ViewParent viewParent = viewRef.get().getParent(); - if (!(viewParent instanceof CoordinatorLayout)) { - return; - } - - CoordinatorLayout parent = (CoordinatorLayout) viewParent; - final int childCount = parent.getChildCount(); - if ((VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) && expanded) { - if (importantForAccessibilityMap == null) { - importantForAccessibilityMap = new HashMap<>(childCount); - } else { - // The important for accessibility values of the child views have been saved already. - return; - } - } - - for (int i = 0; i < childCount; i++) { - final View child = parent.getChildAt(i); - if (child == viewRef.get()) { - continue; - } - if (expanded) { - // Saves the important for accessibility value of the child view. - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { - importantForAccessibilityMap.put(child, child.getImportantForAccessibility()); - } - if (UPDATE_IMPORTANT_FOR_ACCESSIBILITY_ON_SIBLINGS) { - ViewCompat.setImportantForAccessibility( - child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - // If the siblings of the sheet have been set to not important for a11y, move the focus - // to the sheet when expanded. - viewRef.get().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - } else { - if (UPDATE_IMPORTANT_FOR_ACCESSIBILITY_ON_SIBLINGS - && importantForAccessibilityMap != null - && importantForAccessibilityMap.containsKey(child)) { - // Restores the original important for accessibility value of the child view. - ViewCompat.setImportantForAccessibility(child, importantForAccessibilityMap.get(child)); - } - importantForAccessibilityMap = null; + View view = viewRef.get(); + if (view instanceof ViewGroup && ((ViewGroup) view).getChildCount() > 0) { + ViewGroup viewContainer = (ViewGroup) view; + View firstNestedChild = viewContainer.getChildAt(0); + if (firstNestedChild != null) { + firstNestedChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } + } else { + view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } } diff --git a/material/java/com/google/android/material/sidesheet/SideSheetCallback.java b/material/java/com/google/android/material/sidesheet/SideSheetCallback.java new file mode 100644 index 000000000..bbfbecf60 --- /dev/null +++ b/material/java/com/google/android/material/sidesheet/SideSheetCallback.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.sidesheet; + +import android.view.View; +import androidx.annotation.NonNull; +import com.google.android.material.sidesheet.Sheet.SheetState; + +/** Callback that monitors side sheet events. */ +public abstract class SideSheetCallback implements SheetCallback { + + /** + * Called when the sheet changes its state. + * + * @param sheet The sheet view. + * @param newState The new state. This should be one of {@link SideSheetBehavior#STATE_DRAGGING}, + * {@link SideSheetBehavior#STATE_SETTLING}, {@link SideSheetBehavior#STATE_EXPANDED} or + * {@link SideSheetBehavior#STATE_HIDDEN}. + */ + @Override + public abstract void onStateChanged(@NonNull View sheet, @SheetState int newState); + + /** + * Called when the sheet is being dragged. + * + * @param sheet The sheet view. + * @param slideOffset The new offset of this sheet within [0,1] range. Offset increases as this + * sheet is moving towards the outward edge. A value of 0 means that the sheet is hidden, and + * a value of 1 means that the sheet is fully expanded. + */ + @Override + public abstract void onSlide(@NonNull View sheet, float slideOffset); + + void onLayout(@NonNull View sheet) {} +} diff --git a/material/java/com/google/android/material/sidesheet/SideSheetDialog.java b/material/java/com/google/android/material/sidesheet/SideSheetDialog.java index b0a51b3ec..641f7dc9d 100644 --- a/material/java/com/google/android/material/sidesheet/SideSheetDialog.java +++ b/material/java/com/google/android/material/sidesheet/SideSheetDialog.java @@ -17,6 +17,8 @@ import com.google.android.material.R; +import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN; + import android.content.Context; import android.view.View; import android.view.Window; @@ -28,7 +30,7 @@ import com.google.android.material.sidesheet.Sheet.StableSheetState; /** Base class for {@link android.app.Dialog}s styled as a side sheet. */ -public class SideSheetDialog extends SheetDialog { +public class SideSheetDialog extends SheetDialog { private static final int SIDE_SHEET_DIALOG_THEME_ATTR = R.attr.sideSheetDialogTheme; private static final int SIDE_SHEET_DIALOG_DEFAULT_THEME_RES = @@ -38,6 +40,23 @@ public SideSheetDialog(@NonNull Context context) { this(context, 0); } + @Override + void addSheetCancelOnHideCallback( + Sheet behavior) { + behavior.addCallback( + new SideSheetCallback() { + @Override + public void onStateChanged(@NonNull View sheet, int newState) { + if (newState == STATE_HIDDEN) { + cancel(); + } + } + + @Override + public void onSlide(@NonNull View sheet, float slideOffset) {} + }); + } + public SideSheetDialog(@NonNull Context context, @StyleRes int theme) { super(context, theme, SIDE_SHEET_DIALOG_THEME_ATTR, SIDE_SHEET_DIALOG_DEFAULT_THEME_RES); // We hide the title bar for any style configuration. Otherwise, there will be a gap @@ -47,25 +66,25 @@ public SideSheetDialog(@NonNull Context context, @StyleRes int theme) { @LayoutRes @Override - protected int getLayoutResId() { + int getLayoutResId() { return R.layout.m3_side_sheet_dialog; } @IdRes @Override - protected int getDialogId() { + int getDialogId() { return R.id.m3_side_sheet; } @NonNull @Override - protected Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet) { + Sheet getBehaviorFromSheet(@NonNull FrameLayout sheet) { return SideSheetBehavior.from(sheet); } @StableSheetState @Override - protected int getStateOnStart() { + int getStateOnStart() { return Sheet.STATE_EXPANDED; } @@ -77,7 +96,7 @@ protected int getStateOnStart() { @NonNull @Override public SideSheetBehavior getBehavior() { - Sheet sheetBehavior = super.getBehavior(); + Sheet sheetBehavior = super.getBehavior(); if (!(sheetBehavior instanceof SideSheetBehavior)) { throw new IllegalStateException("The view is not associated with SideSheetBehavior"); } diff --git a/material/java/com/google/android/material/sidesheet/res/values/attrs.xml b/material/java/com/google/android/material/sidesheet/res/values/attrs.xml index 21c65ef95..315015c94 100644 --- a/material/java/com/google/android/material/sidesheet/res/values/attrs.xml +++ b/material/java/com/google/android/material/sidesheet/res/values/attrs.xml @@ -17,30 +17,33 @@ - + - + - + - + - + - + - - - + + + + + + diff --git a/material/java/com/google/android/material/slider/BaseSlider.java b/material/java/com/google/android/material/slider/BaseSlider.java index 92bb99ca4..9b22e9053 100644 --- a/material/java/com/google/android/material/slider/BaseSlider.java +++ b/material/java/com/google/android/material/slider/BaseSlider.java @@ -236,8 +236,8 @@ abstract class BaseSlider< private static final int DEFAULT_LABEL_ANIMATION_ENTER_DURATION = 83; private static final int DEFAULT_LABEL_ANIMATION_EXIT_DURATION = 117; - private static final int LABEL_ANIMATION_ENTER_DURATION_ATTR = R.attr.motionDurationLong2; - private static final int LABEL_ANIMATION_EXIT_DURATION_ATTR = R.attr.motionDurationMedium1; + private static final int LABEL_ANIMATION_ENTER_DURATION_ATTR = R.attr.motionDurationMedium4; + private static final int LABEL_ANIMATION_EXIT_DURATION_ATTR = R.attr.motionDurationShort3; private static final int LABEL_ANIMATION_ENTER_EASING_ATTR = R.attr.motionEasingEmphasizedInterpolator; private static final int LABEL_ANIMATION_EXIT_EASING_ATTR = @@ -270,8 +270,6 @@ abstract class BaseSlider< private int minTrackSidePadding; private int defaultThumbRadius; private int defaultTrackHeight; - private int defaultTickActiveRadius; - private int defaultTickInactiveRadius; @Dimension(unit = Dimension.PX) private int minTouchTargetSize; @@ -300,8 +298,6 @@ abstract class BaseSlider< private float stepSize = 0.0f; private float[] ticksCoordinates; private boolean tickVisible = true; - private int tickActiveRadius; - private int tickInactiveRadius; private int trackWidth; private boolean forceDrawCompatHalo; private boolean isLongPress = false; @@ -407,9 +403,6 @@ private void loadResources(@NonNull Resources resources) { defaultThumbRadius = resources.getDimensionPixelSize(R.dimen.mtrl_slider_thumb_radius); defaultTrackHeight = resources.getDimensionPixelSize(R.dimen.mtrl_slider_track_height); - defaultTickActiveRadius = resources.getDimensionPixelSize(R.dimen.mtrl_slider_tick_radius); - defaultTickInactiveRadius = resources.getDimensionPixelSize(R.dimen.mtrl_slider_tick_radius); - labelPadding = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_padding); } @@ -499,10 +492,6 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle setTrackHeight(a.getDimensionPixelSize(R.styleable.Slider_trackHeight, 0)); - setTickActiveRadius(a.getDimensionPixelSize(R.styleable.Slider_tickRadiusActive, 0)); - - setTickInactiveRadius(a.getDimensionPixelSize(R.styleable.Slider_tickRadiusInactive, 0)); - setLabelBehavior(a.getInt(R.styleable.Slider_labelBehavior, LABEL_FLOATING)); if (!a.getBoolean(R.styleable.Slider_android_enabled, true)) { @@ -515,12 +504,8 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle private boolean maybeIncreaseTrackSidePadding() { int increasedSidePaddingByThumb = max(thumbRadius - defaultThumbRadius, 0); int increasedSidePaddingByTrack = max((trackHeight - defaultTrackHeight) / 2, 0); - int increasedSidePaddingByActiveTick = max(tickActiveRadius - defaultTickActiveRadius, 0); - int increasedSidePaddingByInactiveTick = max(tickInactiveRadius - defaultTickInactiveRadius, 0); int newTrackSidePadding = - minTrackSidePadding + max(max(increasedSidePaddingByThumb, increasedSidePaddingByTrack), - max(increasedSidePaddingByActiveTick, increasedSidePaddingByInactiveTick)); - + minTrackSidePadding + max(increasedSidePaddingByThumb, increasedSidePaddingByTrack); if (trackSidePadding == newTrackSidePadding) { return false; } @@ -1296,56 +1281,6 @@ public void setTrackHeight(@IntRange(from = 0) @Dimension int trackHeight) { } } - /** - * Returns the radius of the active tick in pixels. - * - * @attr ref com.google.android.material.R.styleable#Slider_activeTickRadius - * @see #setTickActiveRadius(int) - */ - @Dimension() - public int getTickActiveRadius() { - return tickActiveRadius; - } - - /** - * Set the radius of the active tick in pixels. - * - * @attr ref com.google.android.material.R.styleable#Slider_activeTickRadius - * @see #getTickActiveRadius() - */ - public void setTickActiveRadius(@IntRange(from = 0) @Dimension int tickActiveRadius) { - if (this.tickActiveRadius != tickActiveRadius) { - this.tickActiveRadius = tickActiveRadius; - activeTicksPaint.setStrokeWidth(tickActiveRadius * 2); - updateWidgetLayout(); - } - } - - /** - * Returns the radius of the inactive tick in pixels. - * - * @attr ref com.google.android.material.R.styleable#Slider_inactiveTickRadius - * @see #setTickInactiveRadius(int) - */ - @Dimension() - public int getTickInactiveRadius() { - return tickInactiveRadius; - } - - /** - * Set the radius of the inactive tick in pixels. - * - * @attr ref com.google.android.material.R.styleable#Slider_inactiveTickRadius - * @see #getTickInactiveRadius() - */ - public void setTickInactiveRadius(@IntRange(from = 0) @Dimension int tickInactiveRadius) { - if (this.tickInactiveRadius != tickInactiveRadius) { - this.tickInactiveRadius = tickInactiveRadius; - inactiveTicksPaint.setStrokeWidth(tickInactiveRadius * 2); - updateWidgetLayout(); - } - } - private void updateWidgetLayout() { boolean sizeChanged = maybeIncreaseWidgetHeight(); boolean sidePaddingChanged = maybeIncreaseTrackSidePadding(); @@ -2325,6 +2260,8 @@ private void setValueForLabel(TooltipDrawable label, float value) { private void invalidateTrack() { inactiveTrackPaint.setStrokeWidth(trackHeight); activeTrackPaint.setStrokeWidth(trackHeight); + inactiveTicksPaint.setStrokeWidth(trackHeight / 2.0f); + activeTicksPaint.setStrokeWidth(trackHeight / 2.0f); } /** diff --git a/material/java/com/google/android/material/slider/res-public/values/public.xml b/material/java/com/google/android/material/slider/res-public/values/public.xml index 828626614..7e1bf2715 100644 --- a/material/java/com/google/android/material/slider/res-public/values/public.xml +++ b/material/java/com/google/android/material/slider/res-public/values/public.xml @@ -29,8 +29,6 @@ - - diff --git a/material/java/com/google/android/material/slider/res/values/attrs.xml b/material/java/com/google/android/material/slider/res/values/attrs.xml index ddccf1551..2bd458d02 100644 --- a/material/java/com/google/android/material/slider/res/values/attrs.xml +++ b/material/java/com/google/android/material/slider/res/values/attrs.xml @@ -59,10 +59,6 @@ - - - - diff --git a/material/java/com/google/android/material/slider/res/values/dimens.xml b/material/java/com/google/android/material/slider/res/values/dimens.xml index 3329204df..244b8a9a1 100644 --- a/material/java/com/google/android/material/slider/res/values/dimens.xml +++ b/material/java/com/google/android/material/slider/res/values/dimens.xml @@ -21,8 +21,6 @@ 16dp 4dp - 1dp - 10dp 1dp diff --git a/material/java/com/google/android/material/slider/res/values/styles.xml b/material/java/com/google/android/material/slider/res/values/styles.xml index 68583df9c..7442bffe2 100644 --- a/material/java/com/google/android/material/slider/res/values/styles.xml +++ b/material/java/com/google/android/material/slider/res/values/styles.xml @@ -25,8 +25,6 @@ @dimen/mtrl_slider_thumb_radius @color/material_slider_active_tick_marks_color @color/material_slider_inactive_tick_marks_color - @dimen/mtrl_slider_tick_radius - @dimen/mtrl_slider_tick_radius @color/material_slider_active_track_color @color/material_slider_inactive_track_color @dimen/mtrl_slider_track_height diff --git a/material/java/com/google/android/material/slider/res/values/tokens.xml b/material/java/com/google/android/material/slider/res/values/tokens.xml index 444b45d5d..830d8fde0 100644 --- a/material/java/com/google/android/material/slider/res/values/tokens.xml +++ b/material/java/com/google/android/material/slider/res/values/tokens.xml @@ -15,11 +15,10 @@ ~ limitations under the License. --> - + - - + ?attr/colorPrimary @@ -38,5 +37,4 @@ ?attr/colorOnSurface 0.38 - diff --git a/material/java/com/google/android/material/snackbar/res/values/styles.xml b/material/java/com/google/android/material/snackbar/res/values/styles.xml index f5140b8f9..20ad30c01 100644 --- a/material/java/com/google/android/material/snackbar/res/values/styles.xml +++ b/material/java/com/google/android/material/snackbar/res/values/styles.xml @@ -80,8 +80,7 @@ @null @dimen/m3_snackbar_margin fade - @macro/m3_comp_snackbar_container_shape - @dimen/m3_comp_snackbar_container_elevation + ?attr/shapeAppearanceCornerExtraSmall @@ -91,8 +90,8 @@ - - - - - sans-serif - sans-serif-medium - - - sans-serif - sans-serif-medium - diff --git a/material/java/com/google/android/material/typography/res/values/tokens.xml b/material/java/com/google/android/material/typography/res/values/tokens.xml index 35cf0f089..8e0001e9b 100644 --- a/material/java/com/google/android/material/typography/res/values/tokens.xml +++ b/material/java/com/google/android/material/typography/res/values/tokens.xml @@ -15,72 +15,78 @@ ~ limitations under the License. --> - + - - + tools:ignore="NewApi"> + - + - - sans-serif - sans-serif-medium + sans-serif + sans-serif-medium - - sans-serif - sans-serif-medium - + sans-serif + sans-serif-medium diff --git a/other_tool/compress_lz4/.vs/compress_lz4/v17/.suo b/other_tool/compress_lz4/.vs/compress_lz4/v17/.suo new file mode 100644 index 000000000..d9809a280 Binary files /dev/null and b/other_tool/compress_lz4/.vs/compress_lz4/v17/.suo differ diff --git a/other_tool/compress_lz4/cmdd b/other_tool/compress_lz4/cmdd new file mode 100644 index 000000000..7c84ca4ef --- /dev/null +++ b/other_tool/compress_lz4/cmdd @@ -0,0 +1 @@ +ls | xargs -I {} bash -c "../compress_lz4.elf '{}'" diff --git a/other_tool/compress_lz4_lottie/compress_lz4_lottie.cpp b/other_tool/compress_lz4/compress_lz4.cpp similarity index 81% rename from other_tool/compress_lz4_lottie/compress_lz4_lottie.cpp rename to other_tool/compress_lz4/compress_lz4.cpp index e3a0aa6fd..bd93196f5 100644 --- a/other_tool/compress_lz4_lottie/compress_lz4_lottie.cpp +++ b/other_tool/compress_lz4/compress_lz4.cpp @@ -4,19 +4,19 @@ #include #include #include "lz4.h" +#include "lz4hc.h" #include #ifndef WINDOWS_PLATFORM #include #endif - using namespace std; #define HEADER "\x02\x4C\x5A\x34" #pragma pack(push, 1) struct HDR { - char hdr[4]; - int size; + uint8_t hdr[4]; + uint32_t size; }; #pragma pack(pop) @@ -59,14 +59,14 @@ static void write_content(const string& file, const string& data, int size) { void work(const string& flarg) { string fl = flarg; size_t ps; - if ((ps = fl.find(".json")) != string::npos) { + if ((ps = fl.find("_lz4")) == string::npos) { string in = get_content(fl); #ifdef WINDOWS_PLATFORM _unlink(fl.c_str()); #else unlink(fl.c_str()); #endif - fl = fl.replace(ps, 5, ".lz4"); + fl += "_lz4"; std::string out; auto cnt = LZ4_compressBound((int)in.size()); HDR hdr = {}; @@ -74,19 +74,19 @@ void work(const string& flarg) { hdr.size = (int)in.size(); out.resize(cnt + sizeof(HDR)); - auto size = (uint32_t)LZ4_compress_default(in.data(), ((char*)out.data() + sizeof(HDR)), - (int)in.size(), cnt); + auto size = (uint32_t)LZ4_compress_HC(in.data(), ((char*)out.data() + sizeof(HDR)), + (int)in.size(), cnt, LZ4HC_CLEVEL_MAX); memcpy((char*)out.data(), &hdr, sizeof(HDR)); write_content(fl, out, (int)size + sizeof(HDR)); } - else if ((ps = fl.find(".lz4")) != string::npos) { + else { string in = get_content(fl); #ifdef WINDOWS_PLATFORM _unlink(fl.c_str()); #else unlink(fl.c_str()); #endif - fl = fl.replace(ps, 4, ".json"); + fl = fl.replace(ps, 4, ""); HDR hdr = {}; memcpy(&hdr, in.data(), sizeof(HDR)); std::string out; @@ -95,17 +95,13 @@ void work(const string& flarg) { (int)in.size() - sizeof(HDR), hdr.size); write_content(fl, out, (int)out.length()); } - else { - cout << "\nError: Require .json or .lz4\n\n"; - return; - } cout << fl << endl; } int main(int argc, char* argv[]) { locale::global(locale("ru_RU.UTF-8")); if (argc < 2) { - cout << "\ncompress_lz4_lottie - usage is: \n\n compress_lz4_lottie \n\n"; + cout << "\ncompress_lz4 - usage is: \n\n compress_lz4 \n\n"; return 1; } for (int i = 1; i < argc; i++) { diff --git a/other_tool/compress_lz4/compress_lz4.sh b/other_tool/compress_lz4/compress_lz4.sh new file mode 100644 index 000000000..0e33da717 --- /dev/null +++ b/other_tool/compress_lz4/compress_lz4.sh @@ -0,0 +1,2 @@ +#!/bin/bash +clang compress_lz4.cpp lz4.c lz4hc.c -lstdc++ -static -static-libgcc -o compress_lz4.elf \ No newline at end of file diff --git a/other_tool/compress_lz4_lottie/compress_lz4_lottie.sln b/other_tool/compress_lz4/compress_lz4.sln similarity index 90% rename from other_tool/compress_lz4_lottie/compress_lz4_lottie.sln rename to other_tool/compress_lz4/compress_lz4.sln index 52a692771..3f0e524fb 100644 --- a/other_tool/compress_lz4_lottie/compress_lz4_lottie.sln +++ b/other_tool/compress_lz4/compress_lz4.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32526.322 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "compress_lz4_lottie", "compress_lz4_lottie.vcxproj", "{92E88E66-DBF5-4936-9AFD-5E8947C9A5CB}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "compress_lz4", "compress_lz4.vcxproj", "{92E88E66-DBF5-4936-9AFD-5E8947C9A5CB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj b/other_tool/compress_lz4/compress_lz4.vcxproj similarity index 94% rename from other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj rename to other_tool/compress_lz4/compress_lz4.vcxproj index 3a7c54da2..f0a1d0a43 100644 --- a/other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj +++ b/other_tool/compress_lz4/compress_lz4.vcxproj @@ -10,7 +10,7 @@ 16.0 Win32Proj {92e88e66-dbf5-4936-9afd-5e8947c9a5cb} - compresslz4lottie + compress_lz4 10.0 @@ -56,11 +56,13 @@ - + + + diff --git a/other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj.filters b/other_tool/compress_lz4/compress_lz4.vcxproj.filters similarity index 82% rename from other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj.filters rename to other_tool/compress_lz4/compress_lz4.vcxproj.filters index 48bf7e99b..b633481ab 100644 --- a/other_tool/compress_lz4_lottie/compress_lz4_lottie.vcxproj.filters +++ b/other_tool/compress_lz4/compress_lz4.vcxproj.filters @@ -15,16 +15,22 @@ - + Исходные файлы Исходные файлы + + Исходные файлы + Файлы заголовков + + Файлы заголовков + \ No newline at end of file diff --git a/other_tool/compress_lz4_lottie/lz4.c b/other_tool/compress_lz4/lz4.c similarity index 100% rename from other_tool/compress_lz4_lottie/lz4.c rename to other_tool/compress_lz4/lz4.c diff --git a/other_tool/compress_lz4_lottie/lz4.h b/other_tool/compress_lz4/lz4.h similarity index 100% rename from other_tool/compress_lz4_lottie/lz4.h rename to other_tool/compress_lz4/lz4.h diff --git a/other_tool/compress_lz4/lz4hc.c b/other_tool/compress_lz4/lz4hc.c new file mode 100644 index 000000000..651f190a0 --- /dev/null +++ b/other_tool/compress_lz4/lz4hc.c @@ -0,0 +1,1637 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +/* note : lz4hc is not an independent module, it requires lz4.h/lz4.c for proper compilation */ + + +/* ************************************* +* Tuning Parameter +***************************************/ + +/*! HEAPMODE : + * Select how stateless HC compression functions like `LZ4_compress_HC()` + * allocate memory for their workspace: + * in stack (0:fastest), or in heap (1:default, requires malloc()). + * Since workspace is rather large, heap mode is recommended. +**/ +#ifndef LZ4HC_HEAPMODE +# define LZ4HC_HEAPMODE 1 +#endif + + +/*=== Dependency ===*/ +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" + + +/*=== Common definitions ===*/ +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined (__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +#define LZ4_COMMONDEFS_ONLY +#ifndef LZ4_SRC_INCLUDED +#include "lz4.c" /* LZ4_count, constants, mem */ +#endif + + +/*=== Enums ===*/ +typedef enum { noDictCtx, usingDictCtxHc } dictCtx_directive; + + +/*=== Constants ===*/ +#define OPTIMAL_ML (int)((ML_MASK-1)+MINMATCH) +#define LZ4_OPT_NUM (1<<12) + + +/*=== Macros ===*/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +#define MAX(a,b) ( (a) > (b) ? (a) : (b) ) +#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) +#define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ +#define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ +/* Make fields passed to, and updated by LZ4HC_encodeSequence explicit */ +#define UPDATABLE(ip, op, anchor) &ip, &op, &anchor + +#define LZ4HC_HASHSIZE 4 +static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } + + +/************************************** +* HC Compression +**************************************/ +static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) +{ + MEM_INIT(hc4->hashTable, 0, sizeof(hc4->hashTable)); + MEM_INIT(hc4->chainTable, 0xFF, sizeof(hc4->chainTable)); +} + +static void LZ4HC_init_internal (LZ4HC_CCtx_internal* hc4, const BYTE* start) +{ + size_t const bufferSize = (size_t)(hc4->end - hc4->prefixStart); + size_t newStartingOffset = bufferSize + hc4->dictLimit; + DEBUGLOG(5, "LZ4HC_init_internal"); + assert(newStartingOffset >= bufferSize); /* check overflow */ + if (newStartingOffset > 1 GB) { + LZ4HC_clearTables(hc4); + newStartingOffset = 0; + } + newStartingOffset += 64 KB; + hc4->nextToUpdate = (U32)newStartingOffset; + hc4->prefixStart = start; + hc4->end = start; + hc4->dictStart = start; + hc4->dictLimit = (U32)newStartingOffset; + hc4->lowLimit = (U32)newStartingOffset; +} + + +/* Update chains up to ip (excluded) */ +LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) +{ + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const BYTE* const prefixPtr = hc4->prefixStart; + U32 const prefixIdx = hc4->dictLimit; + U32 const target = (U32)(ip - prefixPtr) + prefixIdx; + U32 idx = hc4->nextToUpdate; + assert(ip >= prefixPtr); + assert(target >= prefixIdx); + + while (idx < target) { + U32 const h = LZ4HC_hashPtr(prefixPtr+idx-prefixIdx); + size_t delta = idx - hashTable[h]; + if (delta>LZ4_DISTANCE_MAX) delta = LZ4_DISTANCE_MAX; + DELTANEXTU16(chainTable, idx) = (U16)delta; + hashTable[h] = idx; + idx++; + } + + hc4->nextToUpdate = target; +} + +/** LZ4HC_countBack() : + * @return : negative value, nb of common bytes before ip/match */ +LZ4_FORCE_INLINE +int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, + const BYTE* const iMin, const BYTE* const mMin) +{ + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + while ( (back > min) + && (ip[back-1] == match[back-1]) ) + back--; + return back; +} + +#if defined(_MSC_VER) +# define LZ4HC_rotl32(x,r) _rotl(x,r) +#else +# define LZ4HC_rotl32(x,r) ((x << r) | (x >> (32 - r))) +#endif + + +static U32 LZ4HC_rotatePattern(size_t const rotate, U32 const pattern) +{ + size_t const bitsToRotate = (rotate & (sizeof(pattern) - 1)) << 3; + if (bitsToRotate == 0) return pattern; + return LZ4HC_rotl32(pattern, (int)bitsToRotate); +} + +/* LZ4HC_countPattern() : + * pattern32 must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) */ +static unsigned +LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) +{ + const BYTE* const iStart = ip; + reg_t const pattern = (sizeof(pattern)==8) ? + (reg_t)pattern32 + (((reg_t)pattern32) << (sizeof(pattern)*4)) : pattern32; + + while (likely(ip < iEnd-(sizeof(pattern)-1))) { + reg_t const diff = LZ4_read_ARCH(ip) ^ pattern; + if (!diff) { ip+=sizeof(pattern); continue; } + ip += LZ4_NbCommonBytes(diff); + return (unsigned)(ip - iStart); + } + + if (LZ4_isLittleEndian()) { + reg_t patternByte = pattern; + while ((ip>= 8; + } + } else { /* big endian */ + U32 bitOffset = (sizeof(pattern)*8) - 8; + while (ip < iEnd) { + BYTE const byte = (BYTE)(pattern >> bitOffset); + if (*ip != byte) break; + ip ++; bitOffset -= 8; + } } + + return (unsigned)(ip - iStart); +} + +/* LZ4HC_reverseCountPattern() : + * pattern must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) + * read using natural platform endianness */ +static unsigned +LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) +{ + const BYTE* const iStart = ip; + + while (likely(ip >= iLow+4)) { + if (LZ4_read32(ip-4) != pattern) break; + ip -= 4; + } + { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianness */ + while (likely(ip>iLow)) { + if (ip[-1] != *bytePtr) break; + ip--; bytePtr--; + } } + return (unsigned)(iStart - ip); +} + +/* LZ4HC_protectDictEnd() : + * Checks if the match is in the last 3 bytes of the dictionary, so reading the + * 4 byte MINMATCH would overflow. + * @returns true if the match index is okay. + */ +static int LZ4HC_protectDictEnd(U32 const dictLimit, U32 const matchIndex) +{ + return ((U32)((dictLimit - 1) - matchIndex) >= 3); +} + +typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; + +typedef struct { + int off; + int len; +} LZ4HC_match_t; + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_InsertAndGetWiderMatch ( + LZ4HC_CCtx_internal* const hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, const BYTE* const iHighLimit, + int longest, + const BYTE** startpos, + const int maxNbAttempts, + const int patternAnalysis, const int chainSwap, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const BYTE* const prefixPtr = hc4->prefixStart; + const U32 prefixIdx = hc4->dictLimit; + const U32 ipIndex = (U32)(ip - prefixPtr) + prefixIdx; + const int withinStartDistance = (hc4->lowLimit + (LZ4_DISTANCE_MAX + 1) > ipIndex); + const U32 lowestMatchIndex = (withinStartDistance) ? hc4->lowLimit : ipIndex - LZ4_DISTANCE_MAX; + const BYTE* const dictStart = hc4->dictStart; + const U32 dictIdx = hc4->lowLimit; + const BYTE* const dictEnd = dictStart + prefixIdx - dictIdx; + int const lookBackLength = (int)(ip-iLowLimit); + int nbAttempts = maxNbAttempts; + U32 matchChainPos = 0; + U32 const pattern = LZ4_read32(ip); + U32 matchIndex; + repeat_state_e repeat = rep_untested; + size_t srcPatternLength = 0; + int offset = 0; + + DEBUGLOG(7, "LZ4HC_InsertAndGetWiderMatch"); + assert(startpos != NULL); + *startpos = ip; /* in case there is no solution */ + /* First Match */ + LZ4HC_Insert(hc4, ip); /* insert all prior positions up to ip (excluded) */ + matchIndex = hashTable[LZ4HC_hashPtr(ip)]; + DEBUGLOG(7, "First candidate match for pos %u found at index %u / %u (lowestMatchIndex)", + ipIndex, matchIndex, lowestMatchIndex); + + while ((matchIndex>=lowestMatchIndex) && (nbAttempts>0)) { + int matchLength=0; + nbAttempts--; + assert(matchIndex < ipIndex); + if (favorDecSpeed && (ipIndex - matchIndex < 8)) { + /* do nothing: + * favorDecSpeed intentionally skips matches with offset < 8 */ + } else if (matchIndex >= prefixIdx) { /* within current Prefix */ + const BYTE* const matchPtr = prefixPtr + (matchIndex - prefixIdx); + assert(matchPtr < ip); + assert(longest >= 1); + if (LZ4_read16(iLowLimit + longest - 1) == LZ4_read16(matchPtr - lookBackLength + longest - 1)) { + if (LZ4_read32(matchPtr) == pattern) { + int const back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, prefixPtr) : 0; + matchLength = MINMATCH + (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + offset = (int)(ipIndex - matchIndex); + *startpos = ip + back; + DEBUGLOG(7, "Found match of len=%i within prefix, offset=%i, back=%i", longest, offset, -back); + } } } + } else { /* lowestMatchIndex <= matchIndex < dictLimit : within Ext Dict */ + const BYTE* const matchPtr = dictStart + (matchIndex - dictIdx); + assert(matchIndex >= dictIdx); + if ( likely(matchIndex <= prefixIdx - 4) + && (LZ4_read32(matchPtr) == pattern) ) { + int back = 0; + const BYTE* vLimit = ip + (prefixIdx - matchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + matchLength = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + if ((ip+matchLength == vLimit) && (vLimit < iHighLimit)) + matchLength += LZ4_count(ip+matchLength, prefixPtr, iHighLimit); + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictStart) : 0; + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + offset = (int)(ipIndex - matchIndex); + *startpos = ip + back; + DEBUGLOG(7, "Found match of len=%i within dict, offset=%i, back=%i", longest, offset, -back); + } } } + + if (chainSwap && matchLength==longest) { /* better match => select a better chain */ + assert(lookBackLength==0); /* search forward only */ + if (matchIndex + (U32)longest <= ipIndex) { + int const kTrigger = 4; + U32 distanceToNextMatch = 1; + int const end = longest - MINMATCH + 1; + int step = 1; + int accel = 1 << kTrigger; + int pos; + for (pos = 0; pos < end; pos += step) { + U32 const candidateDist = DELTANEXTU16(chainTable, matchIndex + (U32)pos); + step = (accel++ >> kTrigger); + if (candidateDist > distanceToNextMatch) { + distanceToNextMatch = candidateDist; + matchChainPos = (U32)pos; + accel = 1 << kTrigger; + } } + if (distanceToNextMatch > 1) { + if (distanceToNextMatch > matchIndex) break; /* avoid overflow */ + matchIndex -= distanceToNextMatch; + continue; + } } } + + { U32 const distNextMatch = DELTANEXTU16(chainTable, matchIndex); + if (patternAnalysis && distNextMatch==1 && matchChainPos==0) { + U32 const matchCandidateIdx = matchIndex-1; + /* may be a repeated pattern */ + if (repeat == rep_untested) { + if ( ((pattern & 0xFFFF) == (pattern >> 16)) + & ((pattern & 0xFF) == (pattern >> 24)) ) { + DEBUGLOG(7, "Repeat pattern detected, char %02X", pattern >> 24); + repeat = rep_confirmed; + srcPatternLength = LZ4HC_countPattern(ip+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + } else { + repeat = rep_not; + } } + if ( (repeat == rep_confirmed) && (matchCandidateIdx >= lowestMatchIndex) + && LZ4HC_protectDictEnd(prefixIdx, matchCandidateIdx) ) { + const int extDict = matchCandidateIdx < prefixIdx; + const BYTE* const matchPtr = extDict ? dictStart + (matchCandidateIdx - dictIdx) : prefixPtr + (matchCandidateIdx - prefixIdx); + if (LZ4_read32(matchPtr) == pattern) { /* good candidate */ + const BYTE* const iLimit = extDict ? dictEnd : iHighLimit; + size_t forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iLimit, pattern) + sizeof(pattern); + if (extDict && matchPtr + forwardPatternLength == iLimit) { + U32 const rotatedPattern = LZ4HC_rotatePattern(forwardPatternLength, pattern); + forwardPatternLength += LZ4HC_countPattern(prefixPtr, iHighLimit, rotatedPattern); + } + { const BYTE* const lowestMatchPtr = extDict ? dictStart : prefixPtr; + size_t backLength = LZ4HC_reverseCountPattern(matchPtr, lowestMatchPtr, pattern); + size_t currentSegmentLength; + if (!extDict + && matchPtr - backLength == prefixPtr + && dictIdx < prefixIdx) { + U32 const rotatedPattern = LZ4HC_rotatePattern((U32)(-(int)backLength), pattern); + backLength += LZ4HC_reverseCountPattern(dictEnd, dictStart, rotatedPattern); + } + /* Limit backLength not go further than lowestMatchIndex */ + backLength = matchCandidateIdx - MAX(matchCandidateIdx - (U32)backLength, lowestMatchIndex); + assert(matchCandidateIdx - backLength >= lowestMatchIndex); + currentSegmentLength = backLength + forwardPatternLength; + /* Adjust to end of pattern if the source pattern fits, otherwise the beginning of the pattern */ + if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ + && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ + U32 const newMatchIndex = matchCandidateIdx + (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ + if (LZ4HC_protectDictEnd(prefixIdx, newMatchIndex)) + matchIndex = newMatchIndex; + else { + /* Can only happen if started in the prefix */ + assert(newMatchIndex >= prefixIdx - 3 && newMatchIndex < prefixIdx && !extDict); + matchIndex = prefixIdx; + } + } else { + U32 const newMatchIndex = matchCandidateIdx - (U32)backLength; /* farthest position in current segment, will find a match of length currentSegmentLength + maybe some back */ + if (!LZ4HC_protectDictEnd(prefixIdx, newMatchIndex)) { + assert(newMatchIndex >= prefixIdx - 3 && newMatchIndex < prefixIdx && !extDict); + matchIndex = prefixIdx; + } else { + matchIndex = newMatchIndex; + if (lookBackLength==0) { /* no back possible */ + size_t const maxML = MIN(currentSegmentLength, srcPatternLength); + if ((size_t)longest < maxML) { + assert(prefixPtr - prefixIdx + matchIndex != ip); + if ((size_t)(ip - prefixPtr) + prefixIdx - matchIndex > LZ4_DISTANCE_MAX) break; + assert(maxML < 2 GB); + longest = (int)maxML; + offset = (int)(ipIndex - matchIndex); + *startpos = ip; + DEBUGLOG(7, "Found repeat pattern match of len=%i, offset=%i", longest, offset); + } + { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); + if (distToNextPattern > matchIndex) break; /* avoid overflow */ + matchIndex -= distToNextPattern; + } } } } } + continue; + } } + } } /* PA optimization */ + + /* follow current chain */ + matchIndex -= DELTANEXTU16(chainTable, matchIndex + matchChainPos); + + } /* while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) */ + + if ( dict == usingDictCtxHc + && nbAttempts > 0 + && ipIndex - lowestMatchIndex < LZ4_DISTANCE_MAX) { + size_t const dictEndOffset = (size_t)(dictCtx->end - dictCtx->prefixStart) + dictCtx->dictLimit; + U32 dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + assert(dictEndOffset <= 1 GB); + matchIndex = dictMatchIndex + lowestMatchIndex - (U32)dictEndOffset; + if (dictMatchIndex>0) DEBUGLOG(7, "dictEndOffset = %zu, dictMatchIndex = %u => relative matchIndex = %i", dictEndOffset, dictMatchIndex, (int)dictMatchIndex - (int)dictEndOffset); + while (ipIndex - matchIndex <= LZ4_DISTANCE_MAX && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->prefixStart - dictCtx->dictLimit + dictMatchIndex; + + if (LZ4_read32(matchPtr) == pattern) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (dictEndOffset - dictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->prefixStart) : 0; + mlt -= back; + if (mlt > longest) { + longest = mlt; + offset = (int)(ipIndex - matchIndex); + *startpos = ip + back; + DEBUGLOG(7, "found match of length %i within extDictCtx", longest); + } } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, dictMatchIndex); + dictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } } } + + { LZ4HC_match_t md; + assert(longest >= 0); + md.len = longest; + md.off = offset; + return md; + } +} + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ + const BYTE* const ip, const BYTE* const iLimit, + const int maxNbAttempts, + const int patternAnalysis, + const dictCtx_directive dict) +{ + const BYTE* uselessPtr = ip; + DEBUGLOG(7, "LZ4HC_InsertAndFindBestMatch"); + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); +} + +/* LZ4HC_encodeSequence() : + * @return : 0 if ok, + * 1 if buffer issue detected */ +LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( + const BYTE** _ip, + BYTE** _op, + const BYTE** _anchor, + int matchLength, + int offset, + limitedOutput_directive limit, + BYTE* oend) +{ +#define ip (*_ip) +#define op (*_op) +#define anchor (*_anchor) + + size_t length; + BYTE* const token = op++; + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) + static const BYTE* start = NULL; + static U32 totalCost = 0; + U32 const pos = (start==NULL) ? 0 : (U32)(anchor - start); + U32 const ll = (U32)(ip - anchor); + U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; + U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; + U32 const cost = 1 + llAdd + ll + 2 + mlAdd; + if (start==NULL) start = anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%4u, match:%4i, offset:%5i, cost:%4u + %5u", + pos, + (U32)(ip - anchor), matchLength, offset, + cost, totalCost); + totalCost += cost; +#endif + + /* Encode Literal length */ + length = (size_t)(ip - anchor); + LZ4_STATIC_ASSERT(notLimited == 0); + /* Check output limit */ + if (limit && ((op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) { + DEBUGLOG(6, "Not enough room to write %i literals (%i bytes remaining)", + (int)length, (int)(oend - op)); + return 1; + } + if (length >= RUN_MASK) { + size_t len = length - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for(; len >= 255 ; len -= 255) *op++ = 255; + *op++ = (BYTE)len; + } else { + *token = (BYTE)(length << ML_BITS); + } + + /* Copy Literals */ + LZ4_wildCopy8(op, anchor, op + length); + op += length; + + /* Encode Offset */ + assert(offset <= LZ4_DISTANCE_MAX ); + assert(offset > 0); + LZ4_writeLE16(op, (U16)(offset)); op += 2; + + /* Encode MatchLength */ + assert(matchLength >= MINMATCH); + length = (size_t)matchLength - MINMATCH; + if (limit && (op + (length / 255) + (1 + LASTLITERALS) > oend)) { + DEBUGLOG(6, "Not enough room to write match length"); + return 1; /* Check output limit */ + } + if (length >= ML_MASK) { + *token += ML_MASK; + length -= ML_MASK; + for(; length >= 510 ; length -= 510) { *op++ = 255; *op++ = 255; } + if (length >= 255) { length -= 255; *op++ = 255; } + *op++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + ip += matchLength; + anchor = ip; + + return 0; +} +#undef ip +#undef op +#undef anchor + +LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( + LZ4HC_CCtx_internal* const ctx, + const char* const source, + char* const dest, + int* srcSizePtr, + int const maxOutputSize, + int maxNbAttempts, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + const int inputSize = *srcSizePtr; + const int patternAnalysis = (maxNbAttempts > 128); /* levels 9+ */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + + BYTE* optr = (BYTE*) dest; + BYTE* op = (BYTE*) dest; + BYTE* oend = op + maxOutputSize; + + const BYTE* start0; + const BYTE* start2 = NULL; + const BYTE* start3 = NULL; + LZ4HC_match_t m0, m1, m2, m3; + const LZ4HC_match_t nomatch = {0, 0}; + + /* init */ + DEBUGLOG(5, "LZ4HC_compress_hashChain (dict?=>%i)", dict); + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* Main Loop */ + while (ip <= mflimit) { + m1 = LZ4HC_InsertAndFindBestMatch(ctx, ip, matchlimit, maxNbAttempts, patternAnalysis, dict); + if (m1.len encode ML1 immediately */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + continue; + } + + if (start0 < ip) { /* first match was skipped at least once */ + if (start2 < ip + m0.len) { /* squeezing ML1 between ML0(original ML1) and ML2 */ + ip = start0; m1 = m0; /* restore initial Match1 */ + } } + + /* Here, start0==ip */ + if ((start2 - ip) < 3) { /* First Match too small : removed */ + ip = start2; + m1 = m2; + goto _Search2; + } + +_Search3: + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + int new_ml = m1.len; + if (new_ml > OPTIMAL_ML) new_ml = OPTIMAL_ML; + if (ip+new_ml > start2 + m2.len - MINMATCH) + new_ml = (int)(start2 - ip) + m2.len - MINMATCH; + correction = new_ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + m2.len -= correction; + } + } + + if (start2 + m2.len <= mflimit) { + m3 = LZ4HC_InsertAndGetWiderMatch(ctx, + start2 + m2.len - 3, start2, matchlimit, m2.len, &start3, + maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + } else { + m3 = nomatch; /* do not search further */ + } + + if (m3.len <= m2.len) { /* No better match => encode ML1 and ML2 */ + /* ip & ref are known; Now for ml */ + if (start2 < ip+m1.len) m1.len = (int)(start2 - ip); + /* Now, encode 2 sequences */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) + goto _dest_overflow; + ip = start2; + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m2.len, m2.off, limit, oend)) { + m1 = m2; + goto _dest_overflow; + } + continue; + } + + if (start3 < ip+m1.len+3) { /* Not enough space for match 2 : remove it */ + if (start3 >= (ip+m1.len)) { /* can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 */ + if (start2 < ip+m1.len) { + int correction = (int)(ip+m1.len - start2); + start2 += correction; + m2.len -= correction; + if (m2.len < MINMATCH) { + start2 = start3; + m2 = m3; + } + } + + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + ip = start3; + m1 = m3; + + start0 = start2; + m0 = m2; + goto _Search2; + } + + start2 = start3; + m2 = m3; + goto _Search3; + } + + /* + * OK, now we have 3 ascending matches; + * let's write the first one ML1. + * ip & ref are known; Now decide ml. + */ + if (start2 < ip+m1.len) { + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + if (m1.len > OPTIMAL_ML) m1.len = OPTIMAL_ML; + if (ip + m1.len > start2 + m2.len - MINMATCH) + m1.len = (int)(start2 - ip) + m2.len - MINMATCH; + correction = m1.len - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + m2.len -= correction; + } + } else { + m1.len = (int)(start2 - ip); + } + } + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + + /* ML2 becomes ML1 */ + ip = start2; m1 = m2; + + /* ML3 becomes ML2 */ + start2 = start3; m2 = m3; + + /* let's find a new ML3 */ + goto _Search3; + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; + /* adapt lastRunSize to fill 'dest' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + LZ4_memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) (((char*)op)-dest); + +_dest_overflow: + if (limit == fillOutput) { + /* Assumption : ip, anchor, ml and ref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing"); + op = optr; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(m1.len >= 0); + if ((size_t)m1.len > maxMlSize) m1.len = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + m1.len >= MFLIMIT) { + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, notLimited, oend); + } } + goto _last_literals; + } + /* compression failed */ + return 0; +} + + +static int LZ4HC_compress_optimal( LZ4HC_CCtx_internal* ctx, + const char* const source, char* dst, + int* srcSizePtr, int dstCapacity, + int const nbSearches, size_t sufficient_len, + const limitedOutput_directive limit, int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed); + + +LZ4_FORCE_INLINE int +LZ4HC_compress_generic_internal ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + typedef enum { lz4hc, lz4opt } lz4hc_strat_e; + typedef struct { + lz4hc_strat_e strat; + int nbSearches; + U32 targetLength; + } cParams_t; + static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { + { lz4hc, 2, 16 }, /* 0, unused */ + { lz4hc, 2, 16 }, /* 1, unused */ + { lz4hc, 2, 16 }, /* 2, unused */ + { lz4hc, 4, 16 }, /* 3 */ + { lz4hc, 8, 16 }, /* 4 */ + { lz4hc, 16, 16 }, /* 5 */ + { lz4hc, 32, 16 }, /* 6 */ + { lz4hc, 64, 16 }, /* 7 */ + { lz4hc, 128, 16 }, /* 8 */ + { lz4hc, 256, 16 }, /* 9 */ + { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ + { lz4opt, 512,128 }, /*11 */ + { lz4opt,16384,LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ + }; + + DEBUGLOG(5, "LZ4HC_compress_generic_internal(src=%p, srcSize=%d)", + src, *srcSizePtr); + + if (limit == fillOutput && dstCapacity < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ + + ctx->end += *srcSizePtr; + if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ + cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); + { cParams_t const cParam = clTable[cLevel]; + HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; + int result; + + if (cParam.strat == lz4hc) { + result = LZ4HC_compress_hashChain(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, limit, dict); + } else { + assert(cParam.strat == lz4opt); + result = LZ4HC_compress_optimal(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, cParam.targetLength, limit, + cLevel == LZ4HC_CLEVEL_MAX, /* ultra mode */ + dict, favor); + } + if (result <= 0) ctx->dirty = 1; + return result; + } +} + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock); + +static int +LZ4HC_compress_generic_noDictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + assert(ctx->dictCtx == NULL); + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, noDictCtx); +} + +static int +LZ4HC_compress_generic_dictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + const size_t position = (size_t)(ctx->end - ctx->prefixStart) + (ctx->dictLimit - ctx->lowLimit); + assert(ctx->dictCtx != NULL); + if (position >= 64 KB) { + ctx->dictCtx = NULL; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else if (position == 0 && *srcSizePtr > 4 KB) { + LZ4_memcpy(ctx, ctx->dictCtx, sizeof(LZ4HC_CCtx_internal)); + LZ4HC_setExternalDict(ctx, (const BYTE *)src); + ctx->compressionLevel = (short)cLevel; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, usingDictCtxHc); + } +} + +static int +LZ4HC_compress_generic ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + if (ctx->dictCtx == NULL) { + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_dictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } +} + + +int LZ4_sizeofStateHC(void) { return (int)sizeof(LZ4_streamHC_t); } + +static size_t LZ4_streamHC_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_streamHC_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_streamHC_t); +#else + return 1; /* effectively disabled */ +#endif +} + +/* state is presumed correctly initialized, + * in which case its size and alignment have already been validate */ +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; + if (!LZ4_isAligned(state, LZ4_streamHC_t_alignment())) return 0; + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)state, compressionLevel); + LZ4HC_init_internal (ctx, (const BYTE*)src); + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, limitedOutput); + else + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, notLimited); +} + +int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + return LZ4_compress_HC_extStateHC_fastReset(state, src, dst, srcSize, dstCapacity, compressionLevel); +} + +int LZ4_compress_HC(const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + int cSize; +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (statePtr==NULL) return 0; +#else + LZ4_streamHC_t state; + LZ4_streamHC_t* const statePtr = &state; +#endif + DEBUGLOG(5, "LZ4_compress_HC") + cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(statePtr); +#endif + return cSize; +} + +/* state is presumed sized correctly (>= sizeof(LZ4_streamHC_t)) */ +int LZ4_compress_HC_destSize(void* state, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + LZ4HC_init_internal(&ctx->internal_donotuse, (const BYTE*) source); + LZ4_setCompressionLevel(ctx, cLevel); + return LZ4HC_compress_generic(&ctx->internal_donotuse, source, dest, sourceSizePtr, targetDestSize, cLevel, fillOutput); +} + + + +/************************************** +* Streaming Functions +**************************************/ +/* allocation */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_streamHC_t* LZ4_createStreamHC(void) +{ + LZ4_streamHC_t* const state = + (LZ4_streamHC_t*)ALLOC_AND_ZERO(sizeof(LZ4_streamHC_t)); + if (state == NULL) return NULL; + LZ4_setCompressionLevel(state, LZ4HC_CLEVEL_DEFAULT); + return state; +} + +int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) +{ + DEBUGLOG(4, "LZ4_freeStreamHC(%p)", LZ4_streamHCPtr); + if (!LZ4_streamHCPtr) return 0; /* support free on NULL */ + FREEMEM(LZ4_streamHCPtr); + return 0; +} +#endif + + +LZ4_streamHC_t* LZ4_initStreamHC (void* buffer, size_t size) +{ + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)buffer; + DEBUGLOG(4, "LZ4_initStreamHC(%p, %u)", buffer, (unsigned)size); + /* check conditions */ + if (buffer == NULL) return NULL; + if (size < sizeof(LZ4_streamHC_t)) return NULL; + if (!LZ4_isAligned(buffer, LZ4_streamHC_t_alignment())) return NULL; + /* init */ + { LZ4HC_CCtx_internal* const hcstate = &(LZ4_streamHCPtr->internal_donotuse); + MEM_INIT(hcstate, 0, sizeof(*hcstate)); } + LZ4_setCompressionLevel(LZ4_streamHCPtr, LZ4HC_CLEVEL_DEFAULT); + return LZ4_streamHCPtr; +} + +/* just a stub */ +void LZ4_resetStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_resetStreamHC_fast (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + LZ4HC_CCtx_internal* const s = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(5, "LZ4_resetStreamHC_fast(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (s->dirty) { + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + } else { + assert(s->end >= s->prefixStart); + s->dictLimit += (U32)(s->end - s->prefixStart); + s->prefixStart = NULL; + s->end = NULL; + s->dictCtx = NULL; + } + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(5, "LZ4_setCompressionLevel(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (compressionLevel < 1) compressionLevel = LZ4HC_CLEVEL_DEFAULT; + if (compressionLevel > LZ4HC_CLEVEL_MAX) compressionLevel = LZ4HC_CLEVEL_MAX; + LZ4_streamHCPtr->internal_donotuse.compressionLevel = (short)compressionLevel; +} + +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor) +{ + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = (favor!=0); +} + +/* LZ4_loadDictHC() : + * LZ4_streamHCPtr is presumed properly initialized */ +int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* dictionary, int dictSize) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_loadDictHC(ctx:%p, dict:%p, dictSize:%d)", LZ4_streamHCPtr, dictionary, dictSize); + assert(LZ4_streamHCPtr != NULL); + if (dictSize > 64 KB) { + dictionary += (size_t)dictSize - 64 KB; + dictSize = 64 KB; + } + /* need a full initialization, there are bad side-effects when using resetFast() */ + { int const cLevel = ctxPtr->compressionLevel; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, cLevel); + } + LZ4HC_init_internal (ctxPtr, (const BYTE*)dictionary); + ctxPtr->end = (const BYTE*)dictionary + dictSize; + if (dictSize >= LZ4HC_HASHSIZE) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); + return dictSize; +} + +void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream) { + working_stream->internal_donotuse.dictCtx = dictionary_stream != NULL ? &(dictionary_stream->internal_donotuse) : NULL; +} + +/* compression */ + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock) +{ + DEBUGLOG(4, "LZ4HC_setExternalDict(%p, %p)", ctxPtr, newBlock); + if (ctxPtr->end >= ctxPtr->prefixStart + 4) + LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ + + /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictStart = ctxPtr->prefixStart; + ctxPtr->dictLimit += (U32)(ctxPtr->end - ctxPtr->prefixStart); + ctxPtr->prefixStart = newBlock; + ctxPtr->end = newBlock; + ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ + + /* cannot reference an extDict and a dictCtx at the same time */ + ctxPtr->dictCtx = NULL; +} + +static int +LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int dstCapacity, + limitedOutput_directive limit) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(5, "LZ4_compressHC_continue_generic(ctx=%p, src=%p, srcSize=%d, limit=%d)", + LZ4_streamHCPtr, src, *srcSizePtr, limit); + assert(ctxPtr != NULL); + /* auto-init if forgotten */ + if (ctxPtr->prefixStart == NULL) LZ4HC_init_internal (ctxPtr, (const BYTE*) src); + + /* Check overflow */ + if ((size_t)(ctxPtr->end - ctxPtr->prefixStart) + ctxPtr->dictLimit > 2 GB) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->prefixStart); + if (dictSize > 64 KB) dictSize = 64 KB; + LZ4_loadDictHC(LZ4_streamHCPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize); + } + + /* Check if blocks follow each other */ + if ((const BYTE*)src != ctxPtr->end) + LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) src + *srcSizePtr; + const BYTE* const dictBegin = ctxPtr->dictStart; + const BYTE* const dictEnd = ctxPtr->dictStart + (ctxPtr->dictLimit - ctxPtr->lowLimit); + if ((sourceEnd > dictBegin) && ((const BYTE*)src < dictEnd)) { + if (sourceEnd > dictEnd) sourceEnd = dictEnd; + ctxPtr->lowLimit += (U32)(sourceEnd - ctxPtr->dictStart); + ctxPtr->dictStart += (U32)(sourceEnd - ctxPtr->dictStart); + /* invalidate dictionary is it's too small */ + if (ctxPtr->dictLimit - ctxPtr->lowLimit < LZ4HC_HASHSIZE) { + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictStart = ctxPtr->prefixStart; + } } } + + return LZ4HC_compress_generic (ctxPtr, src, dst, srcSizePtr, dstCapacity, ctxPtr->compressionLevel, limit); +} + +int LZ4_compress_HC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int srcSize, int dstCapacity) +{ + DEBUGLOG(5, "LZ4_compress_HC_continue"); + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, limitedOutput); + else + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, notLimited); +} + +int LZ4_compress_HC_continue_destSize (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int* srcSizePtr, int targetDestSize) +{ + return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, fillOutput); +} + + + +/* LZ4_saveDictHC : + * save history content + * into a user-provided buffer + * which is then used to continue compression + */ +int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize) +{ + LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse; + int const prefixSize = (int)(streamPtr->end - streamPtr->prefixStart); + DEBUGLOG(5, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize); + assert(prefixSize >= 0); + if (dictSize > 64 KB) dictSize = 64 KB; + if (dictSize < 4) dictSize = 0; + if (dictSize > prefixSize) dictSize = prefixSize; + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) + LZ4_memmove(safeBuffer, streamPtr->end - dictSize, (size_t)dictSize); + { U32 const endIndex = (U32)(streamPtr->end - streamPtr->prefixStart) + streamPtr->dictLimit; + streamPtr->end = (safeBuffer == NULL) ? NULL : (const BYTE*)safeBuffer + dictSize; + streamPtr->prefixStart = (const BYTE*)safeBuffer; + streamPtr->dictLimit = endIndex - (U32)dictSize; + streamPtr->lowLimit = endIndex - (U32)dictSize; + streamPtr->dictStart = streamPtr->prefixStart; + if (streamPtr->nextToUpdate < streamPtr->dictLimit) + streamPtr->nextToUpdate = streamPtr->dictLimit; + } + return dictSize; +} + + +/*************************************************** +* Deprecated Functions +***************************************************/ + +/* These functions currently generate deprecation warnings */ + +/* Wrappers for deprecated compression functions */ +int LZ4_compressHC(const char* src, char* dst, int srcSize) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2(const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_withStateHC (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2_withStateHC (void* state, const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, LZ4_compressBound(srcSize)); } +int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, maxDstSize); } + + +/* Deprecated streaming functions */ +int LZ4_sizeofStreamStateHC(void) { return sizeof(LZ4_streamHC_t); } + +/* state is presumed correctly sized, aka >= sizeof(LZ4_streamHC_t) + * @return : 0 on success, !=0 if error */ +int LZ4_resetStreamStateHC(void* state, char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_initStreamHC(state, sizeof(*hc4)); + if (hc4 == NULL) return 1; /* init failed */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return 0; +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +void* LZ4_createHC (const char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_createStreamHC(); + if (hc4 == NULL) return NULL; /* not enough memory */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return hc4; +} + +int LZ4_freeHC (void* LZ4HC_Data) +{ + if (!LZ4HC_Data) return 0; /* support free on NULL */ + FREEMEM(LZ4HC_Data); + return 0; +} +#endif + +int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, notLimited); +} + +int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int dstCapacity, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, dstCapacity, cLevel, limitedOutput); +} + +char* LZ4_slideInputBufferHC(void* LZ4HC_Data) +{ + LZ4HC_CCtx_internal* const s = &((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse; + const BYTE* const bufferStart = s->prefixStart - s->dictLimit + s->lowLimit; + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)LZ4HC_Data, s->compressionLevel); + /* ugly conversion trick, required to evade (const char*) -> (char*) cast-qual warning :( */ + return (char*)(uptrval)bufferStart; +} + + +/* ================================================ + * LZ4 Optimal parser (levels [LZ4HC_CLEVEL_OPT_MIN - LZ4HC_CLEVEL_MAX]) + * ===============================================*/ +typedef struct { + int price; + int off; + int mlen; + int litlen; +} LZ4HC_optimal_t; + +/* price in bytes */ +LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) +{ + int price = litlen; + assert(litlen >= 0); + if (litlen >= (int)RUN_MASK) + price += 1 + ((litlen-(int)RUN_MASK) / 255); + return price; +} + + +/* requires mlen >= MINMATCH */ +LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) +{ + int price = 1 + 2 ; /* token + 16-bit offset */ + assert(litlen >= 0); + assert(mlen >= MINMATCH); + + price += LZ4HC_literalsPrice(litlen); + + if (mlen >= (int)(ML_MASK+MINMATCH)) + price += 1 + ((mlen-(int)(ML_MASK+MINMATCH)) / 255); + + return price; +} + + + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, + const BYTE* ip, const BYTE* const iHighLimit, + int minLen, int nbSearches, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + LZ4HC_match_t const match0 = { 0 , 0 }; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + ** so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + LZ4HC_match_t md = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + if (md.len <= minLen) return match0; + if (favorDecSpeed) { + if ((md.len>18) & (md.len<=36)) md.len=18; /* favor shortcut */ + } + return md; +} + + +static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, + const char* const source, + char* dst, + int* srcSizePtr, + int dstCapacity, + int const nbSearches, + size_t sufficient_len, + const limitedOutput_directive limit, + int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + int retval = 0; +#define TRAILING_LITERALS 3 +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4HC_optimal_t* const opt = (LZ4HC_optimal_t*)ALLOC(sizeof(LZ4HC_optimal_t) * (LZ4_OPT_NUM + TRAILING_LITERALS)); +#else + LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* ~64 KB, which is a bit large for stack... */ +#endif + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + BYTE* op = (BYTE*) dst; + BYTE* opSaved = (BYTE*) dst; + BYTE* oend = op + dstCapacity; + int ovml = MINMATCH; /* overflow - last sequence */ + int ovoff = 0; + + /* init */ +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + if (opt == NULL) goto _return_label; +#endif + DEBUGLOG(5, "LZ4HC_compress_optimal(dst=%p, dstCapa=%u)", dst, (unsigned)dstCapacity); + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; + + /* Main Loop */ + while (ip <= mflimit) { + int const llen = (int)(ip - anchor); + int best_mlen, best_off; + int cur, last_match_pos = 0; + + LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + if (firstMatch.len==0) { ip++; continue; } + + if ((size_t)firstMatch.len > sufficient_len) { + /* good enough solution : immediate encoding */ + int const firstML = firstMatch.len; + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), firstML, firstMatch.off, limit, oend) ) { /* updates ip, op and anchor */ + ovml = firstML; + ovoff = firstMatch.off; + goto _dest_overflow; + } + continue; + } + + /* set prices for first positions (literals) */ + { int rPos; + for (rPos = 0 ; rPos < MINMATCH ; rPos++) { + int const cost = LZ4HC_literalsPrice(llen + rPos); + opt[rPos].mlen = 1; + opt[rPos].off = 0; + opt[rPos].litlen = llen + rPos; + opt[rPos].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + rPos, cost, opt[rPos].litlen); + } } + /* set prices using initial match */ + { int mlen = MINMATCH; + int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + int const offset = firstMatch.off; + assert(matchML < LZ4_OPT_NUM); + for ( ; mlen <= matchML ; mlen++) { + int const cost = LZ4HC_sequencePrice(llen, mlen); + opt[mlen].mlen = mlen; + opt[mlen].off = offset; + opt[mlen].litlen = llen; + opt[mlen].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", + mlen, cost, mlen); + } } + last_match_pos = firstMatch.len; + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + + /* check further positions */ + for (cur = 1; cur < last_match_pos; cur++) { + const BYTE* const curPtr = ip + cur; + LZ4HC_match_t newMatch; + + if (curPtr > mflimit) break; + DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", + cur, opt[cur].price, opt[cur+1].price, cur+1); + if (fullUpdate) { + /* not useful to search here if next position has same (or lower) cost */ + if ( (opt[cur+1].price <= opt[cur].price) + /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ + && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) + continue; + } else { + /* not useful to search here if next position has same (or lower) cost */ + if (opt[cur+1].price <= opt[cur].price) continue; + } + + DEBUGLOG(7, "search at rPos:%u", cur); + if (fullUpdate) + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + else + /* only test matches of minimum length; slightly faster, but misses a few bytes */ + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches, dict, favorDecSpeed); + if (!newMatch.len) continue; + + if ( ((size_t)newMatch.len > sufficient_len) + || (newMatch.len + cur >= LZ4_OPT_NUM) ) { + /* immediate encoding */ + best_mlen = newMatch.len; + best_off = newMatch.off; + last_match_pos = cur + 1; + goto encode; + } + + /* before match : set price with literals at beginning */ + { int const baseLitlen = opt[cur].litlen; + int litlen; + for (litlen = 1; litlen < MINMATCH; litlen++) { + int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); + int const pos = cur + litlen; + if (price < opt[pos].price) { + opt[pos].mlen = 1; /* literal */ + opt[pos].off = 0; + opt[pos].litlen = baseLitlen+litlen; + opt[pos].price = price; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", + pos, price, opt[pos].litlen); + } } } + + /* set prices using match at position = cur */ + { int const matchML = newMatch.len; + int ml = MINMATCH; + + assert(cur + newMatch.len < LZ4_OPT_NUM); + for ( ; ml <= matchML ; ml++) { + int const pos = cur + ml; + int const offset = newMatch.off; + int price; + int ll; + DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", + pos, last_match_pos); + if (opt[cur].mlen == 1) { + ll = opt[cur].litlen; + price = ((cur > ll) ? opt[cur - ll].price : 0) + + LZ4HC_sequencePrice(ll, ml); + } else { + ll = 0; + price = opt[cur].price + LZ4HC_sequencePrice(0, ml); + } + + assert((U32)favorDecSpeed <= 1); + if (pos > last_match_pos+TRAILING_LITERALS + || price <= opt[pos].price - (int)favorDecSpeed) { + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", + pos, price, ml); + assert(pos < LZ4_OPT_NUM); + if ( (ml == matchML) /* last pos of last match */ + && (last_match_pos < pos) ) + last_match_pos = pos; + opt[pos].mlen = ml; + opt[pos].off = offset; + opt[pos].litlen = ll; + opt[pos].price = price; + } } } + /* complete following positions with literals */ + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + } /* for (cur = 1; cur <= last_match_pos; cur++) */ + + assert(last_match_pos < LZ4_OPT_NUM + TRAILING_LITERALS); + best_mlen = opt[last_match_pos].mlen; + best_off = opt[last_match_pos].off; + cur = last_match_pos - best_mlen; + +encode: /* cur, last_match_pos, best_mlen, best_off must be set */ + assert(cur < LZ4_OPT_NUM); + assert(last_match_pos >= 1); /* == 1 when only one candidate */ + DEBUGLOG(6, "reverse traversal, looking for shortest path (last_match_pos=%i)", last_match_pos); + { int candidate_pos = cur; + int selected_matchLength = best_mlen; + int selected_offset = best_off; + while (1) { /* from end to beginning */ + int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ + int const next_offset = opt[candidate_pos].off; + DEBUGLOG(7, "pos %i: sequence length %i", candidate_pos, selected_matchLength); + opt[candidate_pos].mlen = selected_matchLength; + opt[candidate_pos].off = selected_offset; + selected_matchLength = next_matchLength; + selected_offset = next_offset; + if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ + assert(next_matchLength > 0); /* can be 1, means literal */ + candidate_pos -= next_matchLength; + } } + + /* encode all recorded sequences in order */ + { int rPos = 0; /* relative position (to ip) */ + while (rPos < last_match_pos) { + int const ml = opt[rPos].mlen; + int const offset = opt[rPos].off; + if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ + rPos += ml; + assert(ml >= MINMATCH); + assert((offset >= 1) && (offset <= LZ4_DISTANCE_MAX)); + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, offset, limit, oend) ) { /* updates ip, op and anchor */ + ovml = ml; + ovoff = offset; + goto _dest_overflow; + } } } + } /* while (ip <= mflimit) */ + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) { /* Check output limit */ + retval = 0; + goto _return_label; + } + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + LZ4_memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + retval = (int) ((char*)op-dst); + goto _return_label; + +_dest_overflow: +if (limit == fillOutput) { + /* Assumption : ip, anchor, ovml and ovref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing (only %i bytes remaining)", (int)(oend-1-opSaved)); + op = opSaved; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(ovml >= 0); + if ((size_t)ovml > maxMlSize) ovml = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + ovml >= MFLIMIT) { + DEBUGLOG(6, "Space to end : %i + ml (%i)", (int)((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1), ovml); + DEBUGLOG(6, "Before : ip = %p, anchor = %p", ip, anchor); + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ovml, ovoff, notLimited, oend); + DEBUGLOG(6, "After : ip = %p, anchor = %p", ip, anchor); + } } + goto _last_literals; +} +_return_label: +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(opt); +#endif + return retval; +} diff --git a/other_tool/compress_lz4/lz4hc.h b/other_tool/compress_lz4/lz4hc.h new file mode 100644 index 000000000..e937acfef --- /dev/null +++ b/other_tool/compress_lz4/lz4hc.h @@ -0,0 +1,413 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 3 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +/* + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + 'dst' buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever `dst` buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); + +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +/*! LZ4_attach_HC_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_STATIC_API void LZ4_attach_HC_dictionary( + LZ4_streamHC_t *working_stream, + const LZ4_streamHC_t *dictionary_stream); + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */ diff --git a/other_tool/compress_lz4_lottie/.vs/compress_lz4_lottie/v17/.suo b/other_tool/compress_lz4_lottie/.vs/compress_lz4_lottie/v17/.suo deleted file mode 100644 index 822d37f0d..000000000 Binary files a/other_tool/compress_lz4_lottie/.vs/compress_lz4_lottie/v17/.suo and /dev/null differ diff --git a/other_tool/compress_lz4_lottie/compress_lz4_lottie.sh b/other_tool/compress_lz4_lottie/compress_lz4_lottie.sh deleted file mode 100644 index f969e81e7..000000000 --- a/other_tool/compress_lz4_lottie/compress_lz4_lottie.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -clang compress_lz4_lottie.cpp lz4.c -lstdc++ -static -static-libgcc -o compress_lz4_lottie.elf