Merge branch 'security_suggestions3' into 'master'

Refactors and correctness improvements

See merge request pixeldroid/PixelDroid!468
This commit is contained in:
Matthieu 2022-08-21 11:14:41 +00:00
commit 56b2d97889
33 changed files with 729 additions and 106 deletions

View File

@ -3,11 +3,6 @@ image: registry.gitlab.com/fdroid/ci-images-client
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
# Basic android and gradle stuff
# Check linting
lintDebug:
@ -18,7 +13,13 @@ lintDebug:
- apt-get install -y openjdk-11-jdk-headless
- update-alternatives --auto java
- ./gradlew checkLicenses
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint --write-verification-metadata sha256
- git diff --quiet gradle/verification-metadata.xml || (echo 'Verification of dependencies failed!' && exit 1)
artifacts:
when: on_failure
paths:
- gradle/verification-metadata.xml
# Make Project
assembleDebug:

View File

@ -15,6 +15,7 @@ jacoco.toolVersion = "0.8.7"
android {
namespace 'org.pixeldroid.app'
compileSdkVersion 32
buildToolsVersion '31.0.0'
compileOptions {
@ -27,7 +28,6 @@ android {
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
}
defaultConfig {
applicationId "org.pixeldroid.app"
minSdkVersion 23
targetSdkVersion 32
versionCode 17

View File

@ -515,11 +515,6 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: androidx.databinding:databinding-ktx:+
name: databinding-ktx
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-process:+
name: lifecycle-process
copyrightHolder: Google Inc.
@ -670,11 +665,6 @@
license: The Apache Software License, Version 2.0
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:viewbinding:+
name: viewbinding
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:+
name: kotlinx-coroutines-core-jvm
copyrightHolder: JetBrains s.r.o. and contributors
@ -767,16 +757,6 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: androidx.databinding:databinding-adapters:+
name: databinding-adapters
copyrightHolder: Google Inc
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.databinding:databinding-runtime:+
name: databinding-runtime
copyrightHolder: Google Inc
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.databinding:databinding-common:+
name: databinding-common
copyrightHolder: Google Inc

View File

@ -66,6 +66,8 @@
-assumenosideeffects class android.util.Log {
public static *** getStackTraceString(...);
public static *** d(...);
public static *** e(...);
public static *** println(...);
public static *** w(...);
public static *** v(...);
public static *** i(...);

View File

@ -68,19 +68,19 @@ class DrawerMenuTest {
// Start the screen of your activity.
onView(withText(R.string.menu_settings)).perform(click())
val themes = getInstrumentation().targetContext.resources.getStringArray(R.array.theme_entries)
//select theme modes
// Select theme modes
onView(withText(R.string.theme_title)).perform(click())
onView(withText(themes[2])).perform(click())
//Select an other theme
// Select an other theme
onView(withText(R.string.theme_title)).perform(click())
onView(withText(themes[0])).perform(click())
//Select the last theme
// Select the last theme
onView(withText(R.string.theme_title)).perform(click())
onView(withText(themes[1])).perform(click())
//Check that we are back in the settings page
// Check that we are back in the settings page
onView(withText(R.string.theme_header)).check(matches(isDisplayed()))
}

View File

@ -65,12 +65,12 @@ class HomeFeedTest {
@Test
@RepeatTest
fun clickingTabOnAlbumShowsNextPhoto() {
//Wait for the feed to load
// Wait for the feed to load
waitForView(R.id.albumPager)
activityScenario.onActivity {
a -> run {
//Pick the second photo
// Pick the second photo
a.findViewById<ViewPager2>(R.id.albumPager).currentItem = 2
}
}
@ -80,7 +80,7 @@ class HomeFeedTest {
@Test
@RepeatTest
fun tabReClickScrollUp() {
//Wait for the feed to load
// Wait for the feed to load
waitForView(R.id.albumPager)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(2))
@ -94,7 +94,7 @@ class HomeFeedTest {
@Test
@RepeatTest
fun hashtag() {
//Wait for the feed to load
// Wait for the feed to load
waitForView(R.id.albumPager)
onView(allOf(withClassName(endsWith("RecyclerView")), not(withId(R.id.material_drawer_recycler_view))))
@ -126,27 +126,27 @@ class HomeFeedTest {
@Test
fun doubleTapLikerWorks() {
Thread.sleep(1000)
//Get initial like count
// Get initial like count
val likes = getText(first(withId(R.id.nlikes)))
val nLikes = likes!!.split(" ")[0].toInt()
//Remove sensitive media warning
// Remove sensitive media warning
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<StatusViewHolder>
(0, clickChildViewWithId(R.id.sensitiveWarning)))
Thread.sleep(100)
//Like the post
// Like the post
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<StatusViewHolder>
(0, clickChildViewWithId(R.id.postPicture)))
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<StatusViewHolder >
(0, clickChildViewWithId(R.id.postPicture)))
//...
// ...
Thread.sleep(100)
//Profit
// Profit
onView(first(withId(R.id.nlikes))).check(matches((withText("${nLikes + 1} Likes"))))
}
@ -216,7 +216,7 @@ class HomeFeedTest {
@Test
fun clickingViewCommentShowsTheComments() {
//Open the comment section
// Open the comment section
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<StatusViewHolder>
(0, clickChildViewWithId(R.id.viewComments)))
@ -227,7 +227,7 @@ class HomeFeedTest {
@Test
fun clickingViewCommentFails() {
//Open the comment section
// Open the comment section
onView(withId(R.id.list))
.perform(actionOnItemAtPosition<StatusViewHolder>
(2, clickChildViewWithId(R.id.viewComments)))

View File

@ -197,11 +197,11 @@ class MockedServerTest {
waitForView(R.id.view_pager)
onView(withId(R.id.view_pager))
.perform(ViewActions.swipeLeft()) // notifications
.perform(ViewActions.swipeLeft()) // camera
.perform(ViewActions.swipeLeft()) // search
.perform(ViewActions.swipeLeft()) // homepage
.perform(ViewActions.swipeLeft()) // should stop at homepage
.perform(ViewActions.swipeLeft()) // Notifications
.perform(ViewActions.swipeLeft()) // Camera
.perform(ViewActions.swipeLeft()) // Search
.perform(ViewActions.swipeLeft()) // Homepage
.perform(ViewActions.swipeLeft()) // Should stop at homepage
activityScenario.onActivity {
assert(it.findViewById<BottomNavigationView>(R.id.tabs).selectedItemId == R.id.page_5)
}
@ -211,16 +211,16 @@ class MockedServerTest {
fun swipingPublicTimelineWorks() {
activityScenario.onActivity {
it.findViewById<BottomNavigationView>(R.id.tabs).selectedItemId = R.id.page_5
} // go to the last tab
} // Go to the last tab
waitForView(R.id.view_pager)
onView(withId(R.id.view_pager))
.perform(ViewActions.swipeRight()) // notifications
.perform(ViewActions.swipeRight()) // camera
.perform(ViewActions.swipeRight()) // search
.perform(ViewActions.swipeRight()) // homepage
.perform(ViewActions.swipeRight()) // should stop at homepage
.perform(ViewActions.swipeRight()) // Notifications
.perform(ViewActions.swipeRight()) // Camera
.perform(ViewActions.swipeRight()) // Search
.perform(ViewActions.swipeRight()) // Homepage
.perform(ViewActions.swipeRight()) // Should stop at homepage
activityScenario.onActivity {
assert(it.findViewById<BottomNavigationView>(R.id.tabs).selectedItemId == R.id.page_1)

View File

@ -101,7 +101,7 @@ class NotificationWorkerTest {
// Check worker returns success (which doesn't mean much, but is a good start)
MatcherAssert.assertThat(result, CoreMatchers.`is`(ListenableWorker.Result.success()))
//Open notification shade
// Open notification shade
uiDevice.openNotification()
uiDevice.wait(Until.hasObject(By.textStartsWith(expectedAppName)), 5000)

View File

@ -96,7 +96,7 @@ class PostCreationActivityTest {
@Ignore("Annoying to deal with and also sometimes the intent is not working as it should")
fun createPost() {
onView(withId(R.id.post_creation_send_button)).perform(click())
// should send on main activity
// Should send on main activity
Thread.sleep(3000)
onView(withId(R.id.list)).check(matches(isDisplayed()))
}
@ -105,7 +105,7 @@ class PostCreationActivityTest {
fun errorShown() {
testScenario!!.onActivity { a -> a.upload_error.visibility = VISIBLE }
onView(withId(R.id.retry_upload_button)).perform(click())
// should send on main activity
// Should send on main activity
onView(withId(R.id.retry_upload_button)).check(matches(not(isDisplayed())))
}
*/

View File

@ -60,7 +60,7 @@ class PostCreationFragmentTest {
private val expectedIntent: Matcher<Intent> = hasAction(Intent.ACTION_CHOOSER)
// image choosing intent
// Image choosing intent
@Test
fun galleryButtonLaunchesGalleryIntent() {
waitForView(R.id.photo_view_button)

View File

@ -51,7 +51,7 @@ class ProfileTest {
@Test
fun clickFollowButton() {
if (onView(ViewMatchers.withText("Unfollow")).isDisplayed()) {
//Currently following
// Currently following
// Unfollow
follow("Follow")
@ -59,7 +59,7 @@ class ProfileTest {
// Follow
follow("Unfollow")
} else if (onView(ViewMatchers.withText("Follow")).isDisplayed()){
//Currently not following
// Currently not following
// Follow
follow("Unfollow")
@ -89,7 +89,7 @@ class ProfileTest {
waitForView(R.id.editButton)
//Check that our own profile opened
// Check that our own profile opened
onView(withId(R.id.editButton)).isDisplayed()
}

View File

@ -106,7 +106,7 @@ private fun waitForViewViewAction(viewId: Int, viewMatcher: Matcher<View>): View
// Loops the main thread for a specified period of time.
// Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception - test fails
} while (System.currentTimeMillis() < endTime) // In case of a timeout we throw an exception - test fails
throw PerformException.Builder()
.withCause(TimeoutException())
.withActionDescription(this.description)
@ -228,7 +228,7 @@ fun atPosition(position: Int, itemMatcher: Matcher<View?>): Matcher<View?> {
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
?: // Has no item on such position
return false
return itemMatcher.matches(viewHolder.itemView)
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.pixeldroid.app">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

File diff suppressed because one or more lines are too long

View File

@ -39,7 +39,7 @@ import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity
import org.pixeldroid.app.utils.BaseThemedWithoutBarActivity
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.ffmpegSafeUri
import org.pixeldroid.app.utils.ffmpegCompliantUri
import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType
import java.io.File
@ -329,25 +329,26 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() {
// Having a meaningful suffix is necessary so that ffmpeg knows what to put in output
val suffix = originalUri.fileExtension(contentResolver)
val file = File.createTempFile("temp_video", ".$suffix")
//val file = File.createTempFile("temp_video", ".webm")
val file = File.createTempFile("temp_video", ".$suffix", cacheDir)
//val file = File.createTempFile("temp_video", ".webm", cacheDir)
model.trackTempFile(file)
val fileUri = file.toUri()
val outputVideoPath = ffmpegSafeUri(fileUri)
val outputVideoPath = ffmpegCompliantUri(fileUri)
val inputUri = model.getPhotoData().value!![position].imageUri
val inputSafePath = ffmpegSafeUri(inputUri)
val ffmpegCompliantUri: String = ffmpegCompliantUri(inputUri)
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(ffmpegSafeUri(inputUri)).mediaInformation
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(ffmpegCompliantUri(inputUri)).mediaInformation
val totalVideoDuration = mediaInformation?.duration?.toFloatOrNull()
val mutedString = if(muted) "-an" else ""
val startString = if(videoStart != null) "-ss $videoStart" else ""
val mutedString = if(muted) "-an" else null
val startString: List<String?> = if(videoStart != null) listOf("-ss", "$videoStart") else listOf(null, null)
val endString = if(videoEnd != null) "-to ${videoEnd - (videoStart ?: 0f)}" else ""
val endString: List<String?> = if(videoEnd != null) listOf("-to", "${videoEnd - (videoStart ?: 0f)}") else listOf(null, null)
val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c copy $mutedString -y $outputVideoPath",
val session: FFmpegSession =
FFmpegKit.executeWithArgumentsAsync(listOfNotNull(startString[0], startString[1], "-i", ffmpegCompliantUri, endString[0], endString[1], "-c", "copy", mutedString, "-y", outputVideoPath).toTypedArray(),
//val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath",
{ session ->
val returnCode = session.returnCode

View File

@ -392,14 +392,14 @@ class CameraFragment : BaseFragment() {
// Get a stable reference of the modifiable image capture use case
imageCapture?.let { imageCapture ->
// Create output file to hold the image
// Create output file to hold the image. CameraX saves a JPEG image to this file,
// so it makes no sense to use another extension here
val photoFile = File.createTempFile(
"cachedPhoto", ".png", context?.cacheDir
"cachedPhoto", ".jpg", context?.cacheDir
)
// Setup image capture metadata
val metadata = Metadata().apply {
// Mirror image when using the front camera
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}

View File

@ -291,11 +291,13 @@ class PhotoEditActivity : BaseThemedWithBarActivity() {
setToolbarWidgetColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorOnSurface))
setToolbarColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorSurface))
setActiveControlsWidgetColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorPrimary))
setFreeStyleCropEnabled(true)
}
val uCrop: UCrop = UCrop.of(initialUri!!, Uri.fromFile(file)).withOptions(options)
uCrop.start(this)
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {

View File

@ -29,7 +29,7 @@ import org.pixeldroid.app.databinding.ActivityVideoEditBinding
import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.postCreation.carousel.dpToPx
import org.pixeldroid.app.utils.BaseThemedWithBarActivity
import org.pixeldroid.app.utils.ffmpegSafeUri
import org.pixeldroid.app.utils.ffmpegCompliantUri
import java.io.File
@ -61,7 +61,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
val uri = intent.getParcelableExtra<Uri>(PhotoEditActivity.PICTURE_URI)!!
videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
val inputVideoPath = ffmpegSafeUri(uri)
val inputVideoPath = ffmpegCompliantUri(uri)
val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation
binding.muter.setOnClickListener {
@ -229,18 +229,19 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
thumbnail: ImageView,
thumbTime: Float,
) {
val file = File.createTempFile("temp_img", ".bmp")
val file = File.createTempFile("temp_img", ".bmp", cacheDir)
tempFiles.add(file)
val fileUri = file.toUri()
val inputSafePath = ffmpegSafeUri(inputUri)
val ffmpegCompliantUri = ffmpegCompliantUri(inputUri)
val outputImagePath =
if(fileUri.toString().startsWith("content://"))
FFmpegKitConfig.getSafParameterForWrite(this, fileUri)
else fileUri.toString()
val session = FFmpegKit.executeAsync(
"-noaccurate_seek -ss $thumbTime -i $inputSafePath -vf scale=${thumbnail.width}:${thumbnail.height} -frames:v 1 -f image2 -y $outputImagePath",
{ session ->
val session = FFmpegKit.executeWithArgumentsAsync(arrayOf(
"-noaccurate_seek", "-ss", "$thumbTime", "-i", ffmpegCompliantUri, "-vf",
"scale=${thumbnail.width}:${thumbnail.height}",
"-frames:v", "1", "-f", "image2", "-y", outputImagePath), { session ->
val state = session.state
val returnCode = session.returnCode
@ -254,7 +255,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() {
// CALLED WHEN SESSION IS EXECUTED
Log.d("VideoEditActivity", "FFmpeg process exited with state $state and rc $returnCode.${session.failStackTrace}")
},
{/* CALLED WHEN SESSION PRINTS LOGS */ }) { /*CALLED WHEN SESSION GENERATES STATISTICS*/ }
{/* CALLED WHEN SESSION PRINTS LOGS */ }, { /*CALLED WHEN SESSION GENERATES STATISTICS*/ })
sessionList.add(session.sessionId)
}

View File

@ -226,7 +226,7 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
)
}
//Convert HTML to clickable text
// Convert HTML to clickable text
postDescription.text =
parseHTMLText(
notification?.status?.content ?: "",

View File

@ -45,7 +45,7 @@ class NotificationsRemoteMediator @Inject constructor(
val maxId = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> {
//No prepend for the moment, might be nice to add later
// No prepend for the moment, might be nice to add later
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> state.lastItemOrNull()?.id
@ -67,7 +67,7 @@ class NotificationsRemoteMediator @Inject constructor(
val endOfPaginationReached = apiResponse.isEmpty()
db.withTransaction {
// clear table in the database
// Clear table in the database
if (loadType == LoadType.REFRESH) {
db.notificationDao().clearFeedContent(user.user_id, user.instance_uri)
}

View File

@ -29,7 +29,7 @@ class HomeFeedRemoteMediator @Inject constructor(
val maxId = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> {
//No prepend for the moment, might be nice to add later
// No prepend for the moment, might be nice to add later
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> state.lastItemOrNull()?.id
@ -52,7 +52,7 @@ class HomeFeedRemoteMediator @Inject constructor(
val endOfPaginationReached = apiResponse.isEmpty()
db.withTransaction {
// clear table in the database
// Clear table in the database
if (loadType == LoadType.REFRESH) {
db.homePostDao().clearFeedContent(user.user_id, user.instance_uri)
}

View File

@ -60,7 +60,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
// Get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(db, dao, mediator))[if(home) "home" else "public", FeedViewModel::class.java] as FeedViewModel<T>

View File

@ -44,7 +44,7 @@ class PublicFeedRemoteMediator @Inject constructor(
val maxId = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> {
//No prepend for the moment, might be nice to add later
// No prepend for the moment, might be nice to add later
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> state.lastItemOrNull()?.id
@ -67,7 +67,7 @@ class PublicFeedRemoteMediator @Inject constructor(
val endOfPaginationReached = apiResponse.isEmpty()
db.withTransaction {
// clear table in the database
// Clear table in the database
if (loadType == LoadType.REFRESH) {
db.publicPostDao().clearFeedContent(user.user_id, user.instance_uri)
}

View File

@ -48,7 +48,7 @@ class CommentFragment : UncachedFeedFragment<Status>() {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
// Get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(
requireActivity(), ViewModelFactory(

View File

@ -35,7 +35,7 @@ class SearchAccountFragment : UncachedFeedFragment<Account>() {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
// Get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Account>(

View File

@ -43,7 +43,7 @@ class SearchHashtagFragment : UncachedFeedFragment<Tag>() {
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
// Get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(requireActivity(), ViewModelFactory(
SearchContentRepository<Tag>(

View File

@ -19,8 +19,13 @@ import kotlin.math.withSign
object BlurHashDecoder {
fun blurHashBitmap(resources: Resources, blurHash: String, width: Int?, height: Int?): BitmapDrawable {
val ratioOr0 = (width?.toFloat() ?: 1f) / (height?.toFloat() ?: 1f)
// The call to `decode` is expensive (costs scale linearly on ratio or 1/ratio), so
// the ratio should be contained within 1/100 and 100 (which seem reasonable)
val ratioOr0 = ((width?.toFloat() ?: 1f) / (height?.toFloat() ?: 1f)).coerceIn(1/100f, 100f)
// Width and/or height may be 0 here (bad information sent by server).
// In that case, we make a square 32x32
val ratio = if (ratioOr0 == 0f) 1f else ratioOr0
return BitmapDrawable(resources,
decode(blurHash,
@ -51,7 +56,7 @@ object BlurHashDecoder {
} else {
val from = 4 + i * 2
val colorEnc = decode83(blurHash, from, from + 2)
decodeAc(colorEnc, maxAc * punch)
decodeAc(colorEnc, maxAc *punch)
}
}
return composeBitmap(width, height, numCompX, numCompY, colors)

View File

@ -96,7 +96,7 @@ fun normalizeDomain(domain: String): String {
.trim(Char::isWhitespace)
}
fun Context.ffmpegSafeUri(inputUri: Uri?): String =
fun Context.ffmpegCompliantUri(inputUri: Uri?): String =
if (inputUri?.scheme == "content")
FFmpegKitConfig.getSafParameterForRead(this, inputUri)
else inputUri.toString()

View File

@ -25,7 +25,7 @@ import java.time.format.DateTimeFormatter
/*
Implements the Pixelfed API
https://docs.pixelfed.org/technical-documentation/api-v1.html
https://docs.pixelfed.org/technical-documentation/api-v1
However, since this is mostly based on the Mastodon API, the documentation there
will be more useful: https://docs.joinmastodon.org/
*/

View File

@ -8,7 +8,7 @@ interface InstanceDao {
@Query("SELECT * FROM instances")
fun getAll(): List<InstanceDatabaseEntity>
@Query("SELECT * FROM instances WHERE uri=:instanceUri LIMIT 1")
@Query("SELECT * FROM instances WHERE uri=:instanceUri")
fun getInstance(instanceUri: String): InstanceDatabaseEntity
/**

View File

@ -27,7 +27,7 @@ interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): List<UserDatabaseEntity>
@Query("SELECT * FROM users WHERE isActive=1 LIMIT 1")
@Query("SELECT * FROM users WHERE isActive=1")
fun getActiveUser(): UserDatabaseEntity?
@Query("UPDATE users SET isActive=0")
@ -39,6 +39,6 @@ interface UserDao {
@Query("DELETE FROM users WHERE isActive=1")
fun deleteActiveUsers()
@Query("SELECT * FROM users WHERE user_id=:id AND instance_uri=:instance_uri LIMIT 1")
@Query("SELECT * FROM users WHERE user_id=:id AND instance_uri=:instance_uri")
fun getUserWithId(id: String, instance_uri: String): UserDatabaseEntity
}

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.7.0'
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0-beta05'
classpath 'com.android.tools.build:gradle:7.3.0-rc01'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

File diff suppressed because it is too large Load Diff