Merge branch 'use_server_settings' into 'master'
Use server settings Closes #258 See merge request pixeldroid/PixelDroid!302
This commit is contained in:
commit
a9a4ae10bf
|
@ -74,13 +74,13 @@ dependencies {
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.3.2'
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
|
||||||
implementation "androidx.browser:browser:1.3.0"
|
implementation "androidx.browser:browser:1.3.0"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha12'
|
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha12'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||||
|
@ -90,16 +90,16 @@ dependencies {
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
|
|
||||||
// Use the most recent version of CameraX
|
// Use the most recent version of CameraX
|
||||||
def cameraX_version = '1.0.0-rc01'
|
def cameraX_version = '1.0.0-rc02'
|
||||||
implementation "androidx.camera:camera-core:${cameraX_version}"
|
implementation "androidx.camera:camera-core:${cameraX_version}"
|
||||||
implementation "androidx.camera:camera-camera2:${cameraX_version}"
|
implementation "androidx.camera:camera-camera2:${cameraX_version}"
|
||||||
// CameraX Lifecycle library
|
// CameraX Lifecycle library
|
||||||
implementation "androidx.camera:camera-lifecycle:$cameraX_version"
|
implementation "androidx.camera:camera-lifecycle:$cameraX_version"
|
||||||
|
|
||||||
// CameraX View class
|
// CameraX View class
|
||||||
implementation 'androidx.camera:camera-view:1.0.0-alpha20'
|
implementation 'androidx.camera:camera-view:1.0.0-alpha21'
|
||||||
|
|
||||||
def room_version = "2.3.0-alpha04"
|
def room_version = "2.3.0-beta01"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$room_version"
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
@ -125,7 +125,7 @@ dependencies {
|
||||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.20'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.20'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
implementation 'com.github.connyduck:sparkbutton:4.1.0'
|
||||||
|
|
||||||
|
|
||||||
implementation 'info.androidhive:imagefilters:1.0.7'
|
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||||
|
|
|
@ -10,22 +10,15 @@ import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.h.pixeldroid.databinding.ActivityLoginBinding
|
import com.h.pixeldroid.databinding.ActivityLoginBinding
|
||||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
import com.h.pixeldroid.utils.*
|
||||||
import com.h.pixeldroid.utils.BaseActivity
|
|
||||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.utils.api.objects.*
|
import com.h.pixeldroid.utils.api.objects.*
|
||||||
import com.h.pixeldroid.utils.db.addUser
|
import com.h.pixeldroid.utils.db.addUser
|
||||||
import com.h.pixeldroid.utils.db.storeInstance
|
import com.h.pixeldroid.utils.db.storeInstance
|
||||||
import com.h.pixeldroid.utils.hasInternet
|
import kotlinx.coroutines.*
|
||||||
import com.h.pixeldroid.utils.normalizeDomain
|
|
||||||
import com.h.pixeldroid.utils.openUrl
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||||
|
@ -125,11 +118,7 @@ class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
private fun registerAppToServer(normalizedDomain: String) {
|
private fun registerAppToServer(normalizedDomain: String) {
|
||||||
|
|
||||||
try{
|
if(!validDomain(normalizedDomain)) failedRegistration(getString(R.string.invalid_domain))
|
||||||
HttpUrl.Builder().host(normalizedDomain.replace("https://", "")).scheme("https").build()
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
return failedRegistration(getString(R.string.invalid_domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
loadingAnimation(true)
|
loadingAnimation(true)
|
||||||
|
@ -138,7 +127,6 @@ class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
supervisorScope { }
|
|
||||||
val credentialsDeferred: Deferred<Application?> = async {
|
val credentialsDeferred: Deferred<Application?> = async {
|
||||||
try {
|
try {
|
||||||
pixelfedAPI.registerApplication(
|
pixelfedAPI.registerApplication(
|
||||||
|
@ -157,7 +145,6 @@ class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
val clientId = credentials?.client_id ?: return@launch failedRegistration()
|
val clientId = credentials?.client_id ?: return@launch failedRegistration()
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString("domain", normalizedDomain)
|
|
||||||
.putString("clientID", clientId)
|
.putString("clientID", clientId)
|
||||||
.putString("clientSecret", credentials.client_secret)
|
.putString("clientSecret", credentials.client_secret)
|
||||||
.apply()
|
.apply()
|
||||||
|
@ -181,18 +168,39 @@ class LoginActivity : BaseActivity() {
|
||||||
normalizedDomain: String,
|
normalizedDomain: String,
|
||||||
clientId: String,
|
clientId: String,
|
||||||
nodeInfoSchemaUrl: String
|
nodeInfoSchemaUrl: String
|
||||||
) {
|
) = coroutineScope {
|
||||||
val nodeInfo = try {
|
|
||||||
|
val nodeInfo: NodeInfo = try {
|
||||||
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl)
|
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl)
|
||||||
} catch (exception: IOException) {
|
} catch (exception: IOException) {
|
||||||
return failedRegistration(getString(R.string.instance_error))
|
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||||
} catch (exception: HttpException) {
|
} catch (exception: HttpException) {
|
||||||
return failedRegistration(getString(R.string.instance_error))
|
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val domain: String = try {
|
||||||
|
if (nodeInfo.hasInstanceEndpointInfo()) {
|
||||||
|
storeInstance(db, nodeInfo)
|
||||||
|
nodeInfo.metadata?.config?.site?.url
|
||||||
|
} else {
|
||||||
|
val instance: Instance = try {
|
||||||
|
pixelfedAPI.instance()
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||||
|
}
|
||||||
|
storeInstance(db, nodeInfo = null, instance = instance)
|
||||||
|
instance.uri
|
||||||
|
}
|
||||||
|
} catch (e: IllegalArgumentException){ null }
|
||||||
|
?: return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||||
|
|
||||||
|
preferences.edit().putString("domain", normalizeDomain(domain)).apply()
|
||||||
|
|
||||||
|
|
||||||
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
|
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
|
||||||
val builder = AlertDialog.Builder(this@LoginActivity)
|
AlertDialog.Builder(this@LoginActivity).apply {
|
||||||
builder.apply {
|
|
||||||
setMessage(R.string.instance_not_pixelfed_warning)
|
setMessage(R.string.instance_not_pixelfed_warning)
|
||||||
setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ ->
|
setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ ->
|
||||||
promptOAuth(normalizedDomain, clientId)
|
promptOAuth(normalizedDomain, clientId)
|
||||||
|
@ -201,13 +209,18 @@ class LoginActivity : BaseActivity() {
|
||||||
loadingAnimation(false)
|
loadingAnimation(false)
|
||||||
wipeSharedSettings()
|
wipeSharedSettings()
|
||||||
}
|
}
|
||||||
}
|
}.show()
|
||||||
// Create the AlertDialog
|
} else if (nodeInfo.metadata?.config?.features?.mobile_apis != true) {
|
||||||
builder.show()
|
AlertDialog.Builder(this@LoginActivity).apply {
|
||||||
|
setMessage(R.string.api_not_enabled_dialog)
|
||||||
|
setNegativeButton(android.R.string.ok) { _, _ ->
|
||||||
|
loadingAnimation(false)
|
||||||
|
wipeSharedSettings()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
promptOAuth(normalizedDomain, clientId)
|
promptOAuth(normalizedDomain, clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,9 +251,6 @@ class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val instanceDeferred = async {
|
|
||||||
pixelfedAPI.instance()
|
|
||||||
}
|
|
||||||
val token = pixelfedAPI.obtainToken(
|
val token = pixelfedAPI.obtainToken(
|
||||||
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
||||||
"authorization_code"
|
"authorization_code"
|
||||||
|
@ -248,20 +258,12 @@ class LoginActivity : BaseActivity() {
|
||||||
if (token.access_token == null) {
|
if (token.access_token == null) {
|
||||||
return@launch failedRegistration(getString(R.string.token_error))
|
return@launch failedRegistration(getString(R.string.token_error))
|
||||||
}
|
}
|
||||||
|
|
||||||
val instance = instanceDeferred.await()
|
|
||||||
|
|
||||||
if (instance.uri == null) {
|
|
||||||
return@launch failedRegistration(getString(R.string.instance_error))
|
|
||||||
}
|
|
||||||
|
|
||||||
storeInstance(db, instance)
|
|
||||||
storeUser(
|
storeUser(
|
||||||
token.access_token,
|
token.access_token,
|
||||||
token.refresh_token,
|
token.refresh_token,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
instance.uri
|
domain
|
||||||
)
|
)
|
||||||
wipeSharedSettings()
|
wipeSharedSettings()
|
||||||
} catch (exception: IOException) {
|
} catch (exception: IOException) {
|
||||||
|
|
|
@ -61,15 +61,16 @@ class MainActivity : BaseActivity() {
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
TraceDroidEmailSender.sendStackTraces("contact@pixeldroid.org", this)
|
|
||||||
|
|
||||||
//get the currently active user
|
//get the currently active user
|
||||||
user = db.userDao().getActiveUser()
|
user = db.userDao().getActiveUser()
|
||||||
|
|
||||||
//Check if we have logged in and gotten an access token
|
//Check if we have logged in and gotten an access token
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
launchActivity(LoginActivity(), firstTime = true)
|
launchActivity(LoginActivity(), firstTime = true)
|
||||||
|
finish()
|
||||||
} else {
|
} else {
|
||||||
|
TraceDroidEmailSender.sendStackTraces("contact@pixeldroid.org", this)
|
||||||
|
|
||||||
setupDrawer()
|
setupDrawer()
|
||||||
|
|
||||||
val tabs: List<() -> Fragment> = listOf(
|
val tabs: List<() -> Fragment> = listOf(
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package com.h.pixeldroid.postCreation
|
package com.h.pixeldroid.postCreation
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ContentResolver
|
import android.app.AlertDialog
|
||||||
import android.content.ContentValues
|
import android.content.*
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -31,7 +29,7 @@ import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
|
||||||
import com.h.pixeldroid.utils.BaseActivity
|
import com.h.pixeldroid.utils.BaseActivity
|
||||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||||
import com.h.pixeldroid.utils.api.objects.Instance
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
@ -44,15 +42,18 @@ import java.io.OutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
private const val TAG = "Post Creation Activity"
|
private const val TAG = "Post Creation Activity"
|
||||||
private const val MORE_PICTURES_REQUEST_CODE = 0xffff
|
private const val MORE_PICTURES_REQUEST_CODE = 0xffff
|
||||||
|
|
||||||
data class PhotoData(
|
data class PhotoData(
|
||||||
var imageUri: Uri,
|
var imageUri: Uri,
|
||||||
|
var size: Long,
|
||||||
var uploadId: String? = null,
|
var uploadId: String? = null,
|
||||||
var progress: Int? = null,
|
var progress: Int? = null,
|
||||||
var imageDescription: String? = null
|
var imageDescription: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
class PostCreationActivity : BaseActivity() {
|
class PostCreationActivity : BaseActivity() {
|
||||||
|
@ -62,6 +63,7 @@ class PostCreationActivity : BaseActivity() {
|
||||||
|
|
||||||
private var positionResult = 0
|
private var positionResult = 0
|
||||||
private var user: UserDatabaseEntity? = null
|
private var user: UserDatabaseEntity? = null
|
||||||
|
private lateinit var instance: InstanceDatabaseEntity
|
||||||
|
|
||||||
private val photoData: ArrayList<PhotoData> = ArrayList()
|
private val photoData: ArrayList<PhotoData> = ArrayList()
|
||||||
|
|
||||||
|
@ -72,29 +74,18 @@ class PostCreationActivity : BaseActivity() {
|
||||||
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
// get image URIs
|
|
||||||
if(intent.clipData != null) {
|
|
||||||
val count = intent.clipData!!.itemCount
|
|
||||||
for (i in 0 until count) {
|
|
||||||
intent.clipData!!.getItemAt(i).uri.let {
|
|
||||||
photoData.add(PhotoData(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user = db.userDao().getActiveUser()
|
user = db.userDao().getActiveUser()
|
||||||
|
|
||||||
val instances = db.instanceDao().getAll()
|
instance = user?.run {
|
||||||
|
db.instanceDao().getAll().first { instanceDatabaseEntity ->
|
||||||
|
instanceDatabaseEntity.uri.contains(instance_uri)
|
||||||
|
}
|
||||||
|
} ?: InstanceDatabaseEntity("", "")
|
||||||
|
|
||||||
binding.postTextInputLayout.counterMaxLength = if (user != null){
|
binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars
|
||||||
val thisInstances =
|
|
||||||
instances.filter { instanceDatabaseEntity ->
|
// get image URIs
|
||||||
instanceDatabaseEntity.uri.contains(user!!.instance_uri)
|
intent.clipData?.let { addPossibleImages(it) }
|
||||||
}
|
|
||||||
thisInstances.first().max_toot_chars
|
|
||||||
} else {
|
|
||||||
Instance.DEFAULT_MAX_TOOT_CHARS
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken = user?.accessToken.orEmpty()
|
accessToken = user?.accessToken.orEmpty()
|
||||||
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||||
|
@ -102,15 +93,15 @@ class PostCreationActivity : BaseActivity() {
|
||||||
val carousel: ImageCarousel = binding.carousel
|
val carousel: ImageCarousel = binding.carousel
|
||||||
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
|
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
|
||||||
carousel.layoutCarouselCallback = {
|
carousel.layoutCarouselCallback = {
|
||||||
//TODO transition instead of at once
|
|
||||||
if(it){
|
if(it){
|
||||||
// Became a carousel
|
// Became a carousel
|
||||||
binding.toolbar3.visibility = VISIBLE
|
binding.toolbarPostCreation.visibility = VISIBLE
|
||||||
} else {
|
} else {
|
||||||
// Became a grid
|
// Became a grid
|
||||||
binding.toolbar3.visibility = INVISIBLE
|
binding.toolbarPostCreation.visibility = INVISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
carousel.maxEntries = instance.albumLimit
|
||||||
carousel.addPhotoButtonCallback = {
|
carousel.addPhotoButtonCallback = {
|
||||||
addPhoto(applicationContext)
|
addPhoto(applicationContext)
|
||||||
}
|
}
|
||||||
|
@ -154,6 +145,63 @@ class PostCreationActivity : BaseActivity() {
|
||||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||||
photoData.removeAt(currentPosition)
|
photoData.removeAt(currentPosition)
|
||||||
carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||||
|
binding.addPhotoButton.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will add as many images as possible to [photoData], from the [clipData], and if
|
||||||
|
* ([photoData].size + [clipData].itemCount) > [albumLimit] then it will only add as many images
|
||||||
|
* as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
|
||||||
|
*/
|
||||||
|
private fun addPossibleImages(clipData: ClipData){
|
||||||
|
var count = clipData.itemCount
|
||||||
|
if(count + photoData.size > instance.albumLimit){
|
||||||
|
AlertDialog.Builder(this).apply {
|
||||||
|
setMessage(getString(R.string.total_exceeds_album_limit).format(instance.albumLimit))
|
||||||
|
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
count = count.coerceAtMost(instance.albumLimit - photoData.size)
|
||||||
|
}
|
||||||
|
if (count + photoData.size >= instance.albumLimit) {
|
||||||
|
// Disable buttons to add more images
|
||||||
|
binding.addPhotoButton.isEnabled = false
|
||||||
|
}
|
||||||
|
for (i in 0 until count) {
|
||||||
|
clipData.getItemAt(i).uri.let {
|
||||||
|
val size: Long =
|
||||||
|
if (it.toString().startsWith("content")) {
|
||||||
|
contentResolver.query(it, null, null, null, null)
|
||||||
|
?.use { cursor ->
|
||||||
|
/* Get the column indexes of the data in the Cursor,
|
||||||
|
* move to the first row in the Cursor, get the data,
|
||||||
|
* and display it.
|
||||||
|
*/
|
||||||
|
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
cursor.getLong(sizeIndex)
|
||||||
|
} ?: 0
|
||||||
|
} else {
|
||||||
|
it.toFile().length()
|
||||||
|
}
|
||||||
|
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
|
||||||
|
if(sizeInkBytes > instance.maxPhotoSize || sizeInkBytes > instance.maxVideoSize){
|
||||||
|
val maxSize = when {
|
||||||
|
instance.maxPhotoSize != instance.maxVideoSize -> {
|
||||||
|
val type = contentResolver.getType(it)
|
||||||
|
if(type?.startsWith("video/") == true){
|
||||||
|
instance.maxVideoSize
|
||||||
|
} else instance.maxPhotoSize
|
||||||
|
}
|
||||||
|
else -> instance.maxPhotoSize
|
||||||
|
}
|
||||||
|
AlertDialog.Builder(this).apply {
|
||||||
|
setMessage(getString(R.string.size_exceeds_instance_limit).format(photoData.size + 1, sizeInkBytes, maxSize))
|
||||||
|
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
photoData.add(PhotoData(imageUri = it, size = size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,21 +226,21 @@ class PostCreationActivity : BaseActivity() {
|
||||||
|
|
||||||
if(path.startsWith("file")) {
|
if(path.startsWith("file")) {
|
||||||
MediaScannerConnection.scanFile(
|
MediaScannerConnection.scanFile(
|
||||||
this,
|
this,
|
||||||
arrayOf(path.toUri().toFile().absolutePath),
|
arrayOf(path.toUri().toFile().absolutePath),
|
||||||
null
|
null
|
||||||
) { path, uri ->
|
) { path, uri ->
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Log.e(
|
Log.e(
|
||||||
"NEW IMAGE SCAN FAILED",
|
"NEW IMAGE SCAN FAILED",
|
||||||
"Tried to scan $path, but it failed"
|
"Tried to scan $path, but it failed"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
button, getString(R.string.save_image_success),
|
button, getString(R.string.save_image_success),
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +253,8 @@ class PostCreationActivity : BaseActivity() {
|
||||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
||||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||||
contentValues.put(
|
contentValues.put(
|
||||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||||
Environment.DIRECTORY_PICTURES
|
Environment.DIRECTORY_PICTURES
|
||||||
)
|
)
|
||||||
val imageUri: Uri =
|
val imageUri: Uri =
|
||||||
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
|
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
|
||||||
|
@ -253,23 +301,7 @@ class PostCreationActivity : BaseActivity() {
|
||||||
val imageUri = data.imageUri
|
val imageUri = data.imageUri
|
||||||
val imageInputStream = contentResolver.openInputStream(imageUri)!!
|
val imageInputStream = contentResolver.openInputStream(imageUri)!!
|
||||||
|
|
||||||
val size =
|
val imagePart = ProgressRequestBody(imageInputStream, data.size)
|
||||||
if (imageUri.toString().startsWith("content")) {
|
|
||||||
contentResolver.query(imageUri, null, null, null, null)
|
|
||||||
?.use { cursor ->
|
|
||||||
/* Get the column indexes of the data in the Cursor,
|
|
||||||
* move to the first row in the Cursor, get the data,
|
|
||||||
* and display it.
|
|
||||||
*/
|
|
||||||
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
|
||||||
cursor.moveToFirst()
|
|
||||||
cursor.getLong(sizeIndex)
|
|
||||||
} ?: 0
|
|
||||||
} else {
|
|
||||||
imageUri.toFile().length()
|
|
||||||
}
|
|
||||||
|
|
||||||
val imagePart = ProgressRequestBody(imageInputStream, size)
|
|
||||||
val requestBody = MultipartBody.Builder()
|
val requestBody = MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
.addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
|
.addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
|
||||||
|
@ -294,26 +326,33 @@ class PostCreationActivity : BaseActivity() {
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ attachment: Attachment ->
|
{ attachment: Attachment ->
|
||||||
data.progress = 0
|
data.progress = 0
|
||||||
data.uploadId = attachment.id!!
|
data.uploadId = attachment.id!!
|
||||||
},
|
},
|
||||||
{ e ->
|
{ e: Throwable ->
|
||||||
binding.uploadError.visibility = View.VISIBLE
|
binding.uploadError.visibility = View.VISIBLE
|
||||||
e.printStackTrace()
|
if(e is HttpException){
|
||||||
postSub?.dispose()
|
binding.uploadErrorTextExplanation.text =
|
||||||
sub.dispose()
|
getString(R.string.upload_error).format(e.code())
|
||||||
},
|
binding.uploadErrorTextExplanation.visibility= VISIBLE
|
||||||
{
|
} else {
|
||||||
data.progress = 100
|
binding.uploadErrorTextExplanation.visibility= View.GONE
|
||||||
if(photoData.all{it.progress == 100 && it.uploadId != null}){
|
}
|
||||||
binding.uploadProgressBar.visibility = View.GONE
|
e.printStackTrace()
|
||||||
binding.uploadCompletedTextview.visibility = View.VISIBLE
|
postSub?.dispose()
|
||||||
post()
|
sub.dispose()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data.progress = 100
|
||||||
|
if (photoData.all { it.progress == 100 && it.uploadId != null }) {
|
||||||
|
binding.uploadProgressBar.visibility = View.GONE
|
||||||
|
binding.uploadCompletedTextview.visibility = View.VISIBLE
|
||||||
|
post()
|
||||||
|
}
|
||||||
|
postSub?.dispose()
|
||||||
|
sub.dispose()
|
||||||
}
|
}
|
||||||
postSub?.dispose()
|
|
||||||
sub.dispose()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,23 +363,23 @@ class PostCreationActivity : BaseActivity() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
try {
|
try {
|
||||||
pixelfedAPI.postStatus(
|
pixelfedAPI.postStatus(
|
||||||
authorization = "Bearer $accessToken",
|
authorization = "Bearer $accessToken",
|
||||||
statusText = description,
|
statusText = description,
|
||||||
media_ids = photoData.mapNotNull { it.uploadId }.toList()
|
media_ids = photoData.mapNotNull { it.uploadId }.toList()
|
||||||
)
|
)
|
||||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
Toast.makeText(applicationContext, getString(R.string.upload_post_success),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
|
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (exception: IOException) {
|
} catch (exception: IOException) {
|
||||||
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
|
Toast.makeText(applicationContext, getString(R.string.upload_post_error),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
Log.e(TAG, exception.toString())
|
Log.e(TAG, exception.toString())
|
||||||
enableButton(true)
|
enableButton(true)
|
||||||
} catch (exception: HttpException) {
|
} catch (exception: HttpException) {
|
||||||
Toast.makeText(applicationContext,getString(R.string.upload_post_failed),
|
Toast.makeText(applicationContext, getString(R.string.upload_post_failed),
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
||||||
enableButton(true)
|
enableButton(true)
|
||||||
}
|
}
|
||||||
|
@ -382,12 +421,10 @@ class PostCreationActivity : BaseActivity() {
|
||||||
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
} else if (requestCode == MORE_PICTURES_REQUEST_CODE) {
|
} else if (requestCode == MORE_PICTURES_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK && data?.clipData != null) {
|
|
||||||
|
|
||||||
val count = data.clipData!!.itemCount
|
if (resultCode == Activity.RESULT_OK && data?.clipData != null) {
|
||||||
for (i in 0 until count) {
|
data.clipData?.let {
|
||||||
val imageUri: Uri = data.clipData!!.getItemAt(i).uri
|
addPossibleImages(it)
|
||||||
photoData.add(PhotoData(imageUri))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||||
|
|
|
@ -13,18 +13,17 @@ import com.h.pixeldroid.R
|
||||||
|
|
||||||
|
|
||||||
class CarouselAdapter(
|
class CarouselAdapter(
|
||||||
@LayoutRes private val itemLayout: Int,
|
@LayoutRes private val itemLayout: Int,
|
||||||
@IdRes private val imageViewId: Int,
|
@IdRes private val imageViewId: Int,
|
||||||
var listener: OnItemClickListener? = null,
|
var listener: OnItemClickListener? = null,
|
||||||
private val imageScaleType: ImageView.ScaleType,
|
private val imageScaleType: ImageView.ScaleType,
|
||||||
private val imagePlaceholder: Drawable?,
|
private val imagePlaceholder: Drawable?,
|
||||||
private val carousel: Boolean
|
private val carousel: Boolean,
|
||||||
|
var maxEntries: Int?,
|
||||||
) : RecyclerView.Adapter<CarouselAdapter.MyViewHolder>() {
|
) : RecyclerView.Adapter<CarouselAdapter.MyViewHolder>() {
|
||||||
|
|
||||||
private val dataList: MutableList<CarouselItem> = mutableListOf()
|
private val dataList: MutableList<CarouselItem> = mutableListOf()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MyViewHolder(itemView: View, imageViewId: Int) : RecyclerView.ViewHolder(itemView) {
|
class MyViewHolder(itemView: View, imageViewId: Int) : RecyclerView.ViewHolder(itemView) {
|
||||||
var img: ImageView = itemView.findViewById(imageViewId)
|
var img: ImageView = itemView.findViewById(imageViewId)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +50,7 @@ class CarouselAdapter(
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return if(carousel) dataList.size
|
return if(carousel) dataList.size
|
||||||
|
else if (maxEntries != null && dataList.size >= maxEntries!!) maxEntries!!
|
||||||
else dataList.size + 1
|
else dataList.size + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,12 @@ class ImageCarousel(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var maxEntries: Int? = null
|
||||||
|
set(value){
|
||||||
|
field = value
|
||||||
|
adapter?.maxEntries = value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -419,12 +425,13 @@ class ImageCarousel(
|
||||||
|
|
||||||
private fun initAdapter() {
|
private fun initAdapter() {
|
||||||
adapter = CarouselAdapter(
|
adapter = CarouselAdapter(
|
||||||
itemLayout = itemLayout,
|
itemLayout = itemLayout,
|
||||||
imageViewId = imageViewId,
|
imageViewId = imageViewId,
|
||||||
listener = onItemClickListener,
|
listener = onItemClickListener,
|
||||||
imageScaleType = imageScaleType,
|
imageScaleType = imageScaleType,
|
||||||
imagePlaceholder = imagePlaceholder,
|
imagePlaceholder = imagePlaceholder,
|
||||||
carousel = layoutCarousel
|
carousel = layoutCarousel,
|
||||||
|
maxEntries = maxEntries
|
||||||
)
|
)
|
||||||
recyclerView.adapter = adapter
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
|
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.utils.api.objects.Account
|
import com.h.pixeldroid.utils.api.objects.Account
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
|
|
@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.h.pixeldroid.R
|
import com.h.pixeldroid.R
|
||||||
|
import okhttp3.HttpUrl
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
@ -22,6 +23,21 @@ fun hasInternet(context: Context): Boolean {
|
||||||
return cm.activeNetwork != null
|
return cm.activeNetwork != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if domain is valid or not
|
||||||
|
*/
|
||||||
|
fun validDomain(domain: String?): Boolean {
|
||||||
|
domain?.apply {
|
||||||
|
try {
|
||||||
|
HttpUrl.Builder().host(replace("https://", "")).scheme("https").build()
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} ?: return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
fun normalizeDomain(domain: String): String {
|
fun normalizeDomain(domain: String): String {
|
||||||
return "https://" + domain
|
return "https://" + domain
|
||||||
.replace("http://", "")
|
.replace("http://", "")
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.h.pixeldroid.utils.api.objects
|
package com.h.pixeldroid.utils.api.objects
|
||||||
|
|
||||||
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_TOOT_CHARS
|
||||||
|
|
||||||
data class Instance (
|
data class Instance (
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val email: String?,
|
val email: String?,
|
||||||
|
@ -9,8 +11,4 @@ data class Instance (
|
||||||
val title: String?,
|
val title: String?,
|
||||||
val uri: String?,
|
val uri: String?,
|
||||||
val version: String?
|
val version: String?
|
||||||
) {
|
)
|
||||||
companion object {
|
|
||||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.h.pixeldroid.utils.api.objects
|
package com.h.pixeldroid.utils.api.objects
|
||||||
|
|
||||||
|
import com.h.pixeldroid.utils.validDomain
|
||||||
|
|
||||||
/*
|
/*
|
||||||
See https://nodeinfo.diaspora.software/schema.html and https://pixelfed.social/api/nodeinfo/2.0.json
|
See https://nodeinfo.diaspora.software/schema.html and https://pixelfed.social/api/nodeinfo/2.0.json
|
||||||
A lot of attributes we don't need are omitted, if in the future they are needed we
|
A lot of attributes we don't need are omitted, if in the future they are needed we
|
||||||
|
@ -11,8 +13,21 @@ data class NodeInfo (
|
||||||
val software: Software?,
|
val software: Software?,
|
||||||
val protocols: List<String>?,
|
val protocols: List<String>?,
|
||||||
val openRegistrations: Boolean?,
|
val openRegistrations: Boolean?,
|
||||||
val metadata: PixelfedMetadata?
|
val metadata: PixelfedMetadata?,
|
||||||
){
|
){
|
||||||
|
/**
|
||||||
|
* Check if this NodeInfo has the fields we need or if we also need to look into the
|
||||||
|
* /api/v1/instance endpoint
|
||||||
|
* This only checks for values that might be in the /api/v1/instance endpoint.
|
||||||
|
*/
|
||||||
|
fun hasInstanceEndpointInfo(): Boolean {
|
||||||
|
return validDomain(metadata?.config?.site?.url)
|
||||||
|
&& !metadata?.config?.site?.name.isNullOrBlank()
|
||||||
|
&& metadata?.config?.uploader?.max_caption_length?.toIntOrNull() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data class Software(
|
data class Software(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val version: String?
|
val version: String?
|
||||||
|
@ -31,7 +46,8 @@ data class NodeInfo (
|
||||||
val open_registration: Boolean?,
|
val open_registration: Boolean?,
|
||||||
val uploader: Uploader?,
|
val uploader: Uploader?,
|
||||||
val activitypub: ActivityPub?,
|
val activitypub: ActivityPub?,
|
||||||
val features: Features?
|
val features: Features?,
|
||||||
|
val site: Site?
|
||||||
){
|
){
|
||||||
data class Uploader(
|
data class Uploader(
|
||||||
val max_photo_size: String?,
|
val max_photo_size: String?,
|
||||||
|
@ -55,6 +71,13 @@ data class NodeInfo (
|
||||||
val stories: Boolean?,
|
val stories: Boolean?,
|
||||||
val video: Boolean?
|
val video: Boolean?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class Site(
|
||||||
|
val name: String?,
|
||||||
|
val domain: String?,
|
||||||
|
val url: String?,
|
||||||
|
val description: String?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import com.h.pixeldroid.utils.api.objects.Notification
|
||||||
PublicFeedStatusDatabaseEntity::class,
|
PublicFeedStatusDatabaseEntity::class,
|
||||||
Notification::class
|
Notification::class
|
||||||
],
|
],
|
||||||
version = 2
|
version = 3
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
|
@ -4,23 +4,20 @@ import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||||
import com.h.pixeldroid.utils.api.objects.Account
|
import com.h.pixeldroid.utils.api.objects.Account
|
||||||
import com.h.pixeldroid.utils.api.objects.Instance
|
import com.h.pixeldroid.utils.api.objects.Instance
|
||||||
|
import com.h.pixeldroid.utils.api.objects.NodeInfo
|
||||||
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_ALBUM_LIMIT
|
||||||
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_PHOTO_SIZE
|
||||||
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_TOOT_CHARS
|
||||||
|
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_VIDEO_SIZE
|
||||||
import com.h.pixeldroid.utils.normalizeDomain
|
import com.h.pixeldroid.utils.normalizeDomain
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
private fun normalizeOrNot(uri: String): String{
|
|
||||||
return if(uri.startsWith("http://localhost")){
|
|
||||||
uri
|
|
||||||
} else {
|
|
||||||
normalizeDomain(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = account.id!!,
|
user_id = account.id!!,
|
||||||
//make sure not to normalize to https when localhost, to allow testing
|
instance_uri = normalizeDomain(instance_uri),
|
||||||
instance_uri = normalizeOrNot(instance_uri),
|
|
||||||
username = account.username!!,
|
username = account.username!!,
|
||||||
display_name = account.getDisplayName(),
|
display_name = account.getDisplayName(),
|
||||||
avatar_static = account.avatar_static.orEmpty(),
|
avatar_static = account.avatar_static.orEmpty(),
|
||||||
|
@ -33,14 +30,24 @@ fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeInstance(db: AppDatabase, instance: Instance) {
|
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
|
||||||
val maxTootChars = instance.max_toot_chars?.toInt() ?: Instance.DEFAULT_MAX_TOOT_CHARS
|
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
|
||||||
val dbInstance = InstanceDatabaseEntity(
|
InstanceDatabaseEntity(
|
||||||
//make sure not to normalize to https when localhost, to allow testing
|
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||||
uri = normalizeOrNot(instance.uri.orEmpty()),
|
title = metadata.config.site.name!!,
|
||||||
title = instance.title.orEmpty(),
|
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||||
max_toot_chars = maxTootChars,
|
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
||||||
thumbnail = instance.thumbnail.orEmpty()
|
//Pixelfed doesn't distinguish between max photo and video size
|
||||||
)
|
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
||||||
|
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT
|
||||||
|
)
|
||||||
|
} ?: instance?.run {
|
||||||
|
InstanceDatabaseEntity(
|
||||||
|
uri = normalizeDomain(uri.orEmpty()),
|
||||||
|
title = title.orEmpty(),
|
||||||
|
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
||||||
|
)
|
||||||
|
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
|
||||||
|
|
||||||
db.instanceDao().insertInstance(dbInstance)
|
db.instanceDao().insertInstance(dbInstance)
|
||||||
}
|
}
|
|
@ -6,8 +6,23 @@ import com.h.pixeldroid.utils.api.objects.Instance
|
||||||
|
|
||||||
@Entity(tableName = "instances")
|
@Entity(tableName = "instances")
|
||||||
data class InstanceDatabaseEntity (
|
data class InstanceDatabaseEntity (
|
||||||
@PrimaryKey var uri: String,
|
@PrimaryKey var uri: String,
|
||||||
var title: String = "",
|
var title: String,
|
||||||
var max_toot_chars: Int = Instance.DEFAULT_MAX_TOOT_CHARS,
|
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
||||||
var thumbnail: String = ""
|
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
||||||
)
|
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
||||||
|
// Mastodon has different file limits for videos, default of 40MB
|
||||||
|
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||||
|
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||||
|
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||||
|
) {
|
||||||
|
companion object{
|
||||||
|
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
||||||
|
// either NodeInfo or the instance endpoint
|
||||||
|
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||||
|
|
||||||
|
const val DEFAULT_MAX_PHOTO_SIZE = 8000
|
||||||
|
const val DEFAULT_MAX_VIDEO_SIZE = 40000
|
||||||
|
const val DEFAULT_ALBUM_LIMIT = 4
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="30dp"
|
||||||
|
android:height="30dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"
|
||||||
|
android:fillColor="#757575"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="false"
|
||||||
|
android:drawable="@drawable/add_photo_alternate_gray_30dp" /> <!-- disabled -->
|
||||||
|
<item android:drawable="@drawable/add_photo_alternate_white_30dp" /> <!-- default -->
|
||||||
|
</selector>
|
||||||
|
|
|
@ -8,14 +8,15 @@
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/upload_error"
|
android:id="@+id/upload_error"
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:elevation="2dp"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.mikepenz.iconics.view.IconicsTextView
|
<com.mikepenz.iconics.view.IconicsTextView
|
||||||
android:id="@+id/upload_error_text_view"
|
android:id="@+id/upload_error_text_view"
|
||||||
|
@ -30,15 +31,29 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.mikepenz.iconics.view.IconicsTextView
|
||||||
|
android:id="@+id/upload_error_text_explanation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#90000000"
|
||||||
|
tools:text="Error code returned by server: 413"
|
||||||
|
android:textColor="@color/colorPrimaryError"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/upload_error_text_view"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/retry_upload_button"
|
android:id="@+id/retry_upload_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="@string/retry"
|
android:text="@string/retry"
|
||||||
app:layout_constraintEnd_toEndOf="@id/upload_error_text_view"
|
app:layout_constraintEnd_toEndOf="@id/upload_error_text_view"
|
||||||
|
app:layout_constraintHorizontal_bias="0.498"
|
||||||
app:layout_constraintStart_toStartOf="@id/upload_error_text_view"
|
app:layout_constraintStart_toStartOf="@id/upload_error_text_view"
|
||||||
app:layout_constraintTop_toBottomOf="@id/upload_error_text_view" />
|
app:layout_constraintTop_toBottomOf="@+id/upload_error_text_explanation" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -135,7 +150,7 @@
|
||||||
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/toolbar3"
|
android:id="@+id/toolbarPostCreation"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="#40000000"
|
android:background="#40000000"
|
||||||
|
@ -192,7 +207,7 @@
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="@string/add_photo"
|
android:contentDescription="@string/add_photo"
|
||||||
android:tooltipText='@string/add_photo'
|
android:tooltipText='@string/add_photo'
|
||||||
android:src="@drawable/add_photo_alternate_white_30dp"
|
android:src="@drawable/add_photo_button"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:background="@drawable/add_photo_alternate_white_30dp"
|
android:background="@drawable/add_photo_button"
|
||||||
android:contentDescription="@string/add_photo" />
|
android:contentDescription="@string/add_photo" />
|
||||||
</com.h.pixeldroid.postCreation.SquareLayout>
|
</com.h.pixeldroid.postCreation.SquareLayout>
|
||||||
|
|
|
@ -38,10 +38,11 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="domain_of_your_instance">Domain of your instance</string>
|
<string name="domain_of_your_instance">Domain of your instance</string>
|
||||||
<string name="connect_to_pixelfed">Connect to Pixelfed</string>
|
<string name="connect_to_pixelfed">Connect to Pixelfed</string>
|
||||||
<string name="login_connection_required_once">You need to be online to be able to add the first account and use PixelDroid :(</string>
|
<string name="login_connection_required_once">You need to be online to be able to add the first account and use PixelDroid :(</string>
|
||||||
<string name="add_account_name">Add Account</string>
|
<string name="api_not_enabled_dialog">The API is not activated on this instance. Contact your administrator to ask them to activate it.</string>
|
||||||
<string name="add_account_description">Add another Pixelfed Account</string>
|
|
||||||
<!-- Drawer -->
|
<!-- Drawer -->
|
||||||
<string name="logout">Log out</string>
|
<string name="logout">Log out</string>
|
||||||
|
<string name="add_account_name">Add Account</string>
|
||||||
|
<string name="add_account_description">Add another Pixelfed Account</string>
|
||||||
<!-- Post creation -->
|
<!-- Post creation -->
|
||||||
<string name="permission_denied">Permission denied</string>
|
<string name="permission_denied">Permission denied</string>
|
||||||
<string name="save_image_failed">Unable to save image</string>
|
<string name="save_image_failed">Unable to save image</string>
|
||||||
|
@ -64,6 +65,10 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="switch_to_carousel">Switch to carousel</string>
|
<string name="switch_to_carousel">Switch to carousel</string>
|
||||||
<string name="save_image_description">Save image description</string>
|
<string name="save_image_description">Save image description</string>
|
||||||
<string name="no_media_description">Add a media description here…</string>
|
<string name="no_media_description">Add a media description here…</string>
|
||||||
|
<string name="total_exceeds_album_limit">You chose more images than the maximum your server allows (%1$s). Images beyond the limit have been ignored.</string>
|
||||||
|
<string name="size_exceeds_instance_limit">Size of image number %1$s in the album exceeds the maximum size allowed by the instance (%2$s kB but the limit is %3$s kB). You might not be able to upload it.</string>
|
||||||
|
<string name="upload_error">Error code returned by server: %1$s</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Post editing -->
|
<!-- Post editing -->
|
||||||
<string name="lbl_brightness">BRIGHTNESS</string>
|
<string name="lbl_brightness">BRIGHTNESS</string>
|
||||||
|
@ -78,6 +83,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="crop_result_error">"Couldn't retrieve image after crop"</string>
|
<string name="crop_result_error">"Couldn't retrieve image after crop"</string>
|
||||||
<string name="image_preview">Preview of the image being edited</string>
|
<string name="image_preview">Preview of the image being edited</string>
|
||||||
<string name="crop_button">Button to crop or rotate the image</string>
|
<string name="crop_button">Button to crop or rotate the image</string>
|
||||||
|
<string name="save_before_returning">Save your edits?</string>
|
||||||
|
<string name="no_cancel_edit">No, cancel edit</string>
|
||||||
|
|
||||||
<!-- Camera -->
|
<!-- Camera -->
|
||||||
<string name="capture_button_alt">Capture</string>
|
<string name="capture_button_alt">Capture</string>
|
||||||
|
@ -193,7 +200,4 @@ Following"</item>
|
||||||
<string name="help_translate">Help translate PixelDroid to your language:</string>
|
<string name="help_translate">Help translate PixelDroid to your language:</string>
|
||||||
<string name="issues_contribute">Report issues or contribute to the application:</string>
|
<string name="issues_contribute">Report issues or contribute to the application:</string>
|
||||||
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
||||||
<string name="save_before_returning">Save your edits?</string>
|
|
||||||
<string name="no_cancel_edit">No, cancel edit</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue