Migration from Dagger to Hilt

This commit is contained in:
Matthieu 2024-02-10 17:43:07 +00:00
parent 05cb615f15
commit 0aa3d86c11
29 changed files with 384 additions and 563 deletions

View File

@ -1,10 +1,13 @@
import com.android.build.api.dsl.ManagedVirtualDevice import com.android.build.api.dsl.ManagedVirtualDevice
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' id("com.android.application")
apply plugin: 'jacoco' id("com.google.dagger.hilt.android")
apply plugin: "kotlin-parcelize" id("kotlin-android")
apply plugin: 'com.google.devtools.ksp' id("jacoco")
id("kotlin-parcelize")
id("com.google.devtools.ksp")
}
android { android {
@ -184,6 +187,9 @@ dependencies {
implementation 'com.google.dagger:dagger:2.50' implementation 'com.google.dagger:dagger:2.50'
ksp 'com.google.dagger:dagger-compiler:2.50' ksp 'com.google.dagger:dagger-compiler:2.50'
implementation("com.google.dagger:hilt-android:2.50")
ksp "com.google.dagger:hilt-compiler:2.50"
implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

View File

@ -78,7 +78,7 @@ class MainActivity : BaseActivity() {
private lateinit var header: AccountHeaderView private lateinit var header: AccountHeaderView
private var user: UserDatabaseEntity? = null private var user: UserDatabaseEntity? = null
private lateinit var model: MainActivityViewModel private val model: MainActivityViewModel by viewModels()
companion object { companion object {
const val ADD_ACCOUNT_IDENTIFIER: Long = -13 const val ADD_ACCOUNT_IDENTIFIER: Long = -13
@ -111,12 +111,6 @@ class MainActivity : BaseActivity() {
} else { } else {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this) sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
val _model: MainActivityViewModel by viewModels {
MainActivityViewModelFactory(application)
}
model = _model
setupDrawer() setupDrawer()
val tabs: List<() -> Fragment> = listOf( val tabs: List<() -> Fragment> = listOf(
{ {
@ -280,13 +274,13 @@ class MainActivity : BaseActivity() {
val remainingUsers = db.userDao().getAll() val remainingUsers = db.userDao().getAll()
if (remainingUsers.isEmpty()){ if (remainingUsers.isEmpty()){
//no more users, start first-time login flow // No more users, start first-time login flow
launchActivity(LoginActivity(), firstTime = true) launchActivity(LoginActivity(), firstTime = true)
} else { } else {
val newActive = remainingUsers.first() val newActive = remainingUsers.first()
db.userDao().activateUser(newActive.user_id, newActive.instance_uri) db.userDao().activateUser(newActive.user_id, newActive.instance_uri)
apiHolder.setToCurrentUser() apiHolder.setToCurrentUser()
//relaunch the app // Relaunch the app
launchActivity(MainActivity(), firstTime = true) launchActivity(MainActivity(), firstTime = true)
} }
} }
@ -334,9 +328,11 @@ class MainActivity : BaseActivity() {
} }
private fun switchUser(userId: String, instance_uri: String) { private fun switchUser(userId: String, instance_uri: String) {
db.userDao().deActivateActiveUsers() db.runInTransaction{
db.userDao().activateUser(userId, instance_uri) db.userDao().deActivateActiveUsers()
apiHolder.setToCurrentUser() db.userDao().activateUser(userId, instance_uri)
apiHolder.setToCurrentUser()
}
} }
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem { private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {

View File

@ -1,25 +1,22 @@
package org.pixeldroid.app package org.pixeldroid.app
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import javax.inject.Inject import javax.inject.Inject
class MainActivityViewModel(application: Application) : AndroidViewModel(application) { @HiltViewModel
class MainActivityViewModel @Inject constructor(
@Inject private val db: AppDatabase
lateinit var db: AppDatabase ): ViewModel() {
// Mutable state flow that will be used internally in the ViewModel, empty list is given as initial value. // Mutable state flow that will be used internally in the ViewModel, empty list is given as initial value.
private val _users = MutableStateFlow(emptyList<UserDatabaseEntity>()) private val _users = MutableStateFlow(emptyList<UserDatabaseEntity>())
@ -29,7 +26,6 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica
init { init {
(application as PixelDroidApplication).getAppComponent().inject(this)
getUsers() getUsers()
} }
@ -41,12 +37,4 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica
} }
} }
} }
} }
class MainActivityViewModelFactory(
val application: Application,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Application::class.java).newInstance(application)
}
}

View File

@ -1,43 +1,28 @@
package org.pixeldroid.app.postCreation package org.pixeldroid.app.postCreation
import android.os.* import android.os.Bundle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostCreationBinding import org.pixeldroid.app.databinding.ActivityPostCreationBinding
import org.pixeldroid.app.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
const val TAG = "Post Creation Activity"
class PostCreationActivity : BaseActivity() { class PostCreationActivity : BaseActivity() {
companion object { companion object {
internal const val PICTURE_DESCRIPTION = "picture_description" internal const val POST_DESCRIPTION = "post_description"
internal const val PICTURE_DESCRIPTIONS = "picture_descriptions"
internal const val POST_REDRAFT = "post_redraft" internal const val POST_REDRAFT = "post_redraft"
internal const val POST_NSFW = "post_nsfw" internal const val POST_NSFW = "post_nsfw"
internal const val TEMP_FILES = "temp_files" internal const val TEMP_FILES = "temp_files"
} }
private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity
private lateinit var binding: ActivityPostCreationBinding private lateinit var binding: ActivityPostCreationBinding
private lateinit var navController: NavController private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
user = db.userDao().getActiveUser()
instance = user?.run {
db.instanceDao().getAll().first { instanceDatabaseEntity ->
instanceDatabaseEntity.uri.contains(instance_uri)
}
} ?: InstanceDatabaseEntity("", "")
binding = ActivityPostCreationBinding.inflate(layoutInflater) binding = ActivityPostCreationBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
val navHostFragment = val navHostFragment =
@ -46,8 +31,5 @@ class PostCreationActivity : BaseActivity() {
navController.setGraph(R.navigation.post_creation_graph) navController.setGraph(R.navigation.post_creation_graph)
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp() = navController.navigateUp() || super.onSupportNavigateUp()
return navController.navigateUp() || super.onSupportNavigateUp()
}
} }

View File

@ -33,12 +33,10 @@ import kotlinx.coroutines.launch
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.FragmentPostCreationBinding import org.pixeldroid.app.databinding.FragmentPostCreationBinding
import org.pixeldroid.app.postCreation.camera.CameraActivity import org.pixeldroid.app.postCreation.camera.CameraActivity
import org.pixeldroid.app.postCreation.camera.CameraFragment
import org.pixeldroid.app.postCreation.carousel.CarouselItem import org.pixeldroid.app.postCreation.carousel.CarouselItem
import org.pixeldroid.app.utils.BaseFragment import org.pixeldroid.app.utils.BaseFragment
import org.pixeldroid.app.utils.bindingLifecycleAware import org.pixeldroid.app.utils.bindingLifecycleAware
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType import org.pixeldroid.app.utils.getMimeType
import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity import org.pixeldroid.media_editor.photoEdit.PhotoEditActivity
@ -48,14 +46,10 @@ import java.io.OutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class PostCreationFragment : BaseFragment() { class PostCreationFragment : BaseFragment() {
private var user: UserDatabaseEntity? = null
private var instance: InstanceDatabaseEntity = InstanceDatabaseEntity("", "")
private var binding: FragmentPostCreationBinding by bindingLifecycleAware() private var binding: FragmentPostCreationBinding by bindingLifecycleAware()
private lateinit var model: PostCreationViewModel private val model: PostCreationViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -72,30 +66,16 @@ class PostCreationFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
instance = user?.run { val instance = user?.run {
db.instanceDao().getAll().first { instanceDatabaseEntity -> db.instanceDao().getInstance(instance_uri)
instanceDatabaseEntity.uri.contains(instance_uri)
}
} ?: InstanceDatabaseEntity("", "") } ?: InstanceDatabaseEntity("", "")
val _model: PostCreationViewModel by activityViewModels { model.getPhotoData().observe(viewLifecycleOwner) { newPhotoData: MutableList<PhotoData>? ->
PostCreationViewModelFactory(
requireActivity().application,
requireActivity().intent.clipData!!,
instance,
requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION),
requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false),
requireActivity().intent.getBooleanExtra(CameraFragment.CAMERA_ACTIVITY_STORY, false),
)
}
model = _model
model.getPhotoData().observe(viewLifecycleOwner) { newPhotoData ->
// update UI // update UI
binding.carousel.addData( binding.carousel.addData(
newPhotoData.map { newPhotoData.orEmpty().map {
CarouselItem( CarouselItem(
it.imageUri, it.imageDescription, it.video, it.imageUri, it.imageDescription, it.video,
it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass, it.videoEncodeProgress, it.videoEncodeStabilizationFirstPass,
@ -103,7 +83,7 @@ class PostCreationFragment : BaseFragment() {
) )
} }
) )
binding.postCreationNextButton.isEnabled = newPhotoData.isNotEmpty() binding.postCreationNextButton.isEnabled = newPhotoData?.isNotEmpty() ?: false
} }
lifecycleScope.launch { lifecycleScope.launch {
@ -227,10 +207,9 @@ class PostCreationFragment : BaseFragment() {
} }
private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data?.clipData != null) { val uris = result.data?.extras?.getParcelableArrayList<Uri>(Intent.EXTRA_STREAM)
result.data?.clipData?.let { if (result.resultCode == Activity.RESULT_OK && uris != null) {
model.setImages(model.addPossibleImages(it)) model.setImages(model.addPossibleImages(uris, emptyList()))
}
} else if (result.resultCode != Activity.RESULT_CANCELED) { } else if (result.resultCode != Activity.RESULT_CANCELED) {
Toast.makeText(requireActivity(), R.string.add_images_error, Toast.LENGTH_SHORT).show() Toast.makeText(requireActivity(), R.string.add_images_error, Toast.LENGTH_SHORT).show()
} }

View File

@ -1,7 +1,6 @@
package org.pixeldroid.app.postCreation package org.pixeldroid.app.postCreation
import android.app.Application import android.content.Context
import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
@ -12,15 +11,16 @@ import android.widget.Toast
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.jarsilio.android.scrambler.exceptions.UnsupportedFileFormatException import com.jarsilio.android.scrambler.exceptions.UnsupportedFileFormatException
import com.jarsilio.android.scrambler.stripMetadata import com.jarsilio.android.scrambler.stripMetadata
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
@ -33,10 +33,10 @@ import kotlinx.parcelize.Parcelize
import okhttp3.MultipartBody import okhttp3.MultipartBody
import org.pixeldroid.app.MainActivity import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.postCreation.camera.CameraFragment
import org.pixeldroid.app.utils.api.objects.Attachment import org.pixeldroid.app.utils.api.objects.Attachment
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.fileExtension import org.pixeldroid.app.utils.fileExtension
import org.pixeldroid.app.utils.getMimeType import org.pixeldroid.app.utils.getMimeType
@ -47,21 +47,10 @@ import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.net.URI import java.net.URI
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.ArrayList
import kotlin.collections.MutableList
import kotlin.collections.MutableMap
import kotlin.collections.arrayListOf
import kotlin.collections.forEach
import kotlin.collections.get
import kotlin.collections.getOrNull
import kotlin.collections.indexOfFirst
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.plus
import kotlin.collections.set import kotlin.collections.set
import kotlin.collections.toMutableList
import kotlin.math.ceil import kotlin.math.ceil
const val TAG = "Post Creation ViewModel"
// Models the UI state for the PostCreationActivity // Models the UI state for the PostCreationActivity
data class PostCreationActivityUiState( data class PostCreationActivityUiState(
@ -108,35 +97,40 @@ data class PhotoData(
var videoEncodeError: Boolean = false, var videoEncodeError: Boolean = false,
) : Parcelable ) : Parcelable
class PostCreationViewModel( @HiltViewModel
application: Application, class PostCreationViewModel @Inject constructor(
clipdata: ClipData? = null, private val state: SavedStateHandle,
val instance: InstanceDatabaseEntity? = null, @ApplicationContext private val applicationContext: Context,
existingDescription: String? = null, db: AppDatabase
existingNSFW: Boolean = false, ): ViewModel() {
storyCreation: Boolean = false,
) : AndroidViewModel(application) {
private var storyPhotoDataBackup: MutableList<PhotoData>? = null private var storyPhotoDataBackup: MutableList<PhotoData>? = null
private val photoData: MutableLiveData<MutableList<PhotoData>> by lazy { private val photoData: MutableLiveData<MutableList<PhotoData>> by lazy {
MutableLiveData<MutableList<PhotoData>>().also { MutableLiveData<MutableList<PhotoData>>(
it.value = clipdata?.let { it1 -> addPossibleImages(it1, mutableListOf()) } addPossibleImages(
} state.get<ArrayList<Uri>>(Intent.EXTRA_STREAM),
state.get<ArrayList<String>>(PostCreationActivity.PICTURE_DESCRIPTIONS),
mutableListOf()
)
)
} }
private val instance = db.instanceDao().getActiveInstance()
@Inject @Inject
lateinit var apiHolder: PixelfedAPIHolder lateinit var apiHolder: PixelfedAPIHolder
private val _uiState: MutableStateFlow<PostCreationActivityUiState> private val _uiState: MutableStateFlow<PostCreationActivityUiState>
init { init {
(application as PixelDroidApplication).getAppComponent().inject(this)
val sharedPreferences = val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(application) PreferenceManager.getDefaultSharedPreferences(applicationContext)
val templateDescription = sharedPreferences.getString("prefill_description", "") ?: "" val templateDescription = sharedPreferences.getString("prefill_description", "") ?: ""
val storyCreation: Boolean = state[CameraFragment.CAMERA_ACTIVITY_STORY] ?: false
_uiState = MutableStateFlow(PostCreationActivityUiState( _uiState = MutableStateFlow(PostCreationActivityUiState(
newPostDescriptionText = existingDescription ?: templateDescription, newPostDescriptionText = state[PostCreationActivity.POST_DESCRIPTION] ?: templateDescription,
nsfw = existingNSFW, nsfw = state[PostCreationActivity.POST_NSFW] ?: false,
maxEntries = if(storyCreation) 1 else instance?.albumLimit, maxEntries = if(storyCreation) 1 else instance?.albumLimit,
storyCreation = storyCreation storyCreation = storyCreation
)) ))
@ -161,32 +155,41 @@ class PostCreationViewModel(
fun getPhotoData(): LiveData<MutableList<PhotoData>> = photoData fun getPhotoData(): LiveData<MutableList<PhotoData>> = photoData
/** /**
* Will add as many images as possible to [photoData], from the [clipData], and if * Will add as many images as possible to [photoData], from the [uris], and if
* ([photoData].size + [clipData].itemCount) > uiState.value.maxEntries then it will only add as many images * ([photoData].size + [uris].size) > uiState.value.maxEntries 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. * as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
*/ */
fun addPossibleImages(clipData: ClipData, previousList: MutableList<PhotoData>? = photoData.value): MutableList<PhotoData> { fun addPossibleImages(
uris: ArrayList<Uri>?,
descriptions: List<String>?,
previousList: MutableList<PhotoData>? = photoData.value,
): MutableList<PhotoData> {
val dataToAdd: ArrayList<PhotoData> = arrayListOf() val dataToAdd: ArrayList<PhotoData> = arrayListOf()
var count = clipData.itemCount var count = uris?.size ?: 0
uiState.value.maxEntries?.let { uiState.value.maxEntries?.let { maxEntries ->
if(count + (previousList?.size ?: 0) > it){ if(count + (previousList?.size ?: 0) > maxEntries){
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy(userMessage = getApplication<PixelDroidApplication>().getString(R.string.total_exceeds_album_limit).format(it)) currentUiState.copy(userMessage = applicationContext.getString(R.string.total_exceeds_album_limit).format(maxEntries))
} }
count = count.coerceAtMost(it - (previousList?.size ?: 0)) count = count.coerceAtMost(maxEntries - (previousList?.size ?: 0))
} }
if (count + (previousList?.size ?: 0) >= it) { if (count + (previousList?.size ?: 0) >= maxEntries) {
// Disable buttons to add more images // Disable buttons to add more images
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy(addPhotoButtonEnabled = false) currentUiState.copy(addPhotoButtonEnabled = false)
} }
} }
for (i in 0 until count) { for ((i, uri) in uris.orEmpty().withIndex()) {
clipData.getItemAt(i).let { val sizeAndVideoPair: Pair<Long, Boolean> =
val sizeAndVideoPair: Pair<Long, Boolean> = getSizeAndVideoValidate(uri, (previousList?.size ?: 0) + dataToAdd.size + 1)
getSizeAndVideoValidate(it.uri, (previousList?.size ?: 0) + dataToAdd.size + 1) dataToAdd.add(
dataToAdd.add(PhotoData(imageUri = it.uri, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second, imageDescription = it.text?.toString())) PhotoData(
} imageUri = uri,
size = sizeAndVideoPair.first,
video = sizeAndVideoPair.second,
imageDescription = descriptions?.getOrNull(i)
)
)
} }
} }
@ -204,7 +207,7 @@ class PostCreationViewModel(
private fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair<Long, Boolean> { private fun getSizeAndVideoValidate(uri: Uri, editPosition: Int): Pair<Long, Boolean> {
val size: Long = val size: Long =
if (uri.scheme =="content") { if (uri.scheme =="content") {
getApplication<PixelDroidApplication>().contentResolver.query(uri, null, null, null, null) applicationContext.contentResolver.query(uri, null, null, null, null)
?.use { cursor -> ?.use { cursor ->
/* Get the column indexes of the data in the Cursor, /* Get the column indexes of the data in the Cursor,
* move to the first row in the Cursor, get the data, * move to the first row in the Cursor, get the data,
@ -221,12 +224,12 @@ class PostCreationViewModel(
} }
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong() val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
val type = uri.getMimeType(getApplication<PixelDroidApplication>().contentResolver) val type = uri.getMimeType(applicationContext.contentResolver)
val isVideo = type.startsWith("video/") val isVideo = type.startsWith("video/")
if (isVideo && !instance!!.videoEnabled) { if (isVideo && !instance!!.videoEnabled) {
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy(userMessage = getApplication<PixelDroidApplication>().getString(R.string.video_not_supported)) currentUiState.copy(userMessage = applicationContext.getString(R.string.video_not_supported))
} }
} }
@ -235,7 +238,7 @@ class PostCreationViewModel(
val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy( currentUiState.copy(
userMessage = getApplication<PixelDroidApplication>().getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize) userMessage = applicationContext.getString(R.string.size_exceeds_instance_limit, editPosition, sizeInkBytes, maxSize)
) )
} }
} }
@ -272,7 +275,7 @@ class PostCreationViewModel(
videoEncodeComplete = false videoEncodeComplete = false
VideoEditActivity.startEncoding(imageUri, null, it, VideoEditActivity.startEncoding(imageUri, null, it,
context = getApplication<PixelDroidApplication>(), context = applicationContext,
registerNewFFmpegSession = ::registerNewFFmpegSession, registerNewFFmpegSession = ::registerNewFFmpegSession,
trackTempFile = ::trackTempFile, trackTempFile = ::trackTempFile,
videoEncodeProgress = ::videoEncodeProgress videoEncodeProgress = ::videoEncodeProgress
@ -387,17 +390,17 @@ class PostCreationViewModel(
} }
for (data: PhotoData in getPhotoData().value ?: emptyList()) { for (data: PhotoData in getPhotoData().value ?: emptyList()) {
val extension = data.imageUri.fileExtension(getApplication<PixelDroidApplication>().contentResolver) val extension = data.imageUri.fileExtension(applicationContext.contentResolver)
val strippedImage = File.createTempFile("temp_img", ".$extension", getApplication<PixelDroidApplication>().cacheDir) val strippedImage = File.createTempFile("temp_img", ".$extension", applicationContext.cacheDir)
val imageUri = data.imageUri val imageUri = data.imageUri
val (strippedOrNot, size) = try { val (strippedOrNot, size) = try {
val orientation = ExifInterface(getApplication<PixelDroidApplication>().contentResolver.openInputStream(imageUri)!!).getAttributeInt( val orientation = ExifInterface(applicationContext.contentResolver.openInputStream(imageUri)!!).getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
stripMetadata(imageUri, strippedImage, getApplication<PixelDroidApplication>().contentResolver) stripMetadata(imageUri, strippedImage, applicationContext.contentResolver)
// Restore EXIF orientation // Restore EXIF orientation
val exifInterface = ExifInterface(strippedImage) val exifInterface = ExifInterface(strippedImage)
@ -409,11 +412,11 @@ class PostCreationViewModel(
strippedImage.delete() strippedImage.delete()
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
val imageInputStream = try { val imageInputStream = try {
getApplication<PixelDroidApplication>().contentResolver.openInputStream(imageUri)!! applicationContext.contentResolver.openInputStream(imageUri)!!
} catch (e: FileNotFoundException){ } catch (e: FileNotFoundException){
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy( currentUiState.copy(
userMessage = getApplication<PixelDroidApplication>().getString(R.string.file_not_found, userMessage = applicationContext.getString(R.string.file_not_found,
data.imageUri) data.imageUri)
) )
} }
@ -425,14 +428,14 @@ class PostCreationViewModel(
if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete() if(imageUri != data.imageUri) File(URI(imageUri.toString())).delete()
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
currentUiState.copy( currentUiState.copy(
userMessage = getApplication<PixelDroidApplication>().getString(R.string.file_not_found, userMessage = applicationContext.getString(R.string.file_not_found,
data.imageUri) data.imageUri)
) )
} }
return return
} }
val type = data.imageUri.getMimeType(getApplication<PixelDroidApplication>().contentResolver) val type = data.imageUri.getMimeType(applicationContext.contentResolver)
val imagePart = ProgressRequestBody(strippedOrNot, size, type) val imagePart = ProgressRequestBody(strippedOrNot, size, type)
val requestBody = MultipartBody.Builder() val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM) .setType(MultipartBody.FORM)
@ -482,7 +485,7 @@ class PostCreationViewModel(
currentUiState.copy( currentUiState.copy(
uploadErrorVisible = true, uploadErrorVisible = true,
uploadErrorExplanationText = if(e is HttpException){ uploadErrorExplanationText = if(e is HttpException){
getApplication<PixelDroidApplication>().getString(R.string.upload_error, e.code()) applicationContext.getString(R.string.upload_error, e.code())
} else "", } else "",
uploadErrorExplanationVisible = e is HttpException, uploadErrorExplanationVisible = e is HttpException,
) )
@ -548,14 +551,14 @@ class PostCreationViewModel(
sensitive = nsfw sensitive = nsfw
) )
} }
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_success), Toast.makeText(applicationContext, applicationContext.getString(R.string.upload_post_success),
Toast.LENGTH_SHORT).show() Toast.LENGTH_SHORT).show()
val intent = Intent(getApplication(), MainActivity::class.java) val intent = Intent(applicationContext, 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
//TODO make the activity launch this instead (and surrounding toasts too) //TODO make the activity launch this instead (and surrounding toasts too)
getApplication<PixelDroidApplication>().startActivity(intent) applicationContext.startActivity(intent)
} catch (exception: IOException) { } catch (exception: IOException) {
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_error), Toast.makeText(applicationContext, 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())
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
@ -564,7 +567,7 @@ class PostCreationViewModel(
) )
} }
} catch (exception: HttpException) { } catch (exception: HttpException) {
Toast.makeText(getApplication(), getApplication<PixelDroidApplication>().getString(R.string.upload_post_failed), Toast.makeText(applicationContext, 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())
_uiState.update { currentUiState -> _uiState.update { currentUiState ->
@ -609,7 +612,7 @@ class PostCreationViewModel(
//Show message saying extraneous pictures were removed but can be restored //Show message saying extraneous pictures were removed but can be restored
newUiState = newUiState.copy( newUiState = newUiState.copy(
userMessage = getApplication<PixelDroidApplication>().getString(R.string.extraneous_pictures_stories) userMessage = applicationContext.getString(R.string.extraneous_pictures_stories)
) )
} }
// Restore if backup not null and first value is unchanged // Restore if backup not null and first value is unchanged
@ -629,10 +632,4 @@ class PostCreationViewModel(
fun updateStoryReactions(checked: Boolean) { _uiState.update { it.copy(storyReactions = checked) } } fun updateStoryReactions(checked: Boolean) { _uiState.update { it.copy(storyReactions = checked) } }
fun updateStoryReplies(checked: Boolean) { _uiState.update { it.copy(storyReplies = checked) } } fun updateStoryReplies(checked: Boolean) { _uiState.update { it.copy(storyReplies = checked) } }
} }
class PostCreationViewModelFactory(val application: Application, val clipdata: ClipData, val instance: InstanceDatabaseEntity, val existingDescription: String?, val existingNSFW: Boolean, val storyCreation: Boolean) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Application::class.java, ClipData::class.java, InstanceDatabaseEntity::class.java, String::class.java, Boolean::class.java, Boolean::class.java).newInstance(application, clipdata, instance, existingDescription, existingNSFW, storyCreation)
}
}

View File

@ -38,7 +38,7 @@ class PostSubmissionFragment : BaseFragment() {
private lateinit var instance: InstanceDatabaseEntity private lateinit var instance: InstanceDatabaseEntity
private var binding: FragmentPostSubmissionBinding by bindingLifecycleAware() private var binding: FragmentPostSubmissionBinding by bindingLifecycleAware()
private lateinit var model: PostCreationViewModel private val model: PostCreationViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -60,23 +60,9 @@ class PostSubmissionFragment : BaseFragment() {
accounts = db.userDao().getAll() accounts = db.userDao().getAll()
instance = user?.run { instance = user?.run {
db.instanceDao().getAll().first { instanceDatabaseEntity -> db.instanceDao().getInstance(instance_uri)
instanceDatabaseEntity.uri.contains(instance_uri)
}
} ?: InstanceDatabaseEntity("", "") } ?: InstanceDatabaseEntity("", "")
val _model: PostCreationViewModel by activityViewModels {
PostCreationViewModelFactory(
requireActivity().application,
requireActivity().intent.clipData!!,
instance,
requireActivity().intent.getStringExtra(PostCreationActivity.PICTURE_DESCRIPTION),
requireActivity().intent.getBooleanExtra(PostCreationActivity.POST_NSFW, false),
requireActivity().intent.getBooleanExtra(CameraFragment.CAMERA_ACTIVITY_STORY, false)
)
}
model = _model
// Display the values from the view model // Display the values from the view model
binding.nsfwSwitch.isChecked = model.uiState.value.nsfw binding.nsfwSwitch.isChecked = model.uiState.value.nsfw
binding.newPostDescriptionInputField.setText(model.uiState.value.newPostDescriptionText) binding.newPostDescriptionInputField.setText(model.uiState.value.newPostDescriptionText)

View File

@ -38,6 +38,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.pixeldroid.app.databinding.FragmentCameraBinding import org.pixeldroid.app.databinding.FragmentCameraBinding
import org.pixeldroid.app.postCreation.PostCreationActivity import org.pixeldroid.app.postCreation.PostCreationActivity
import org.pixeldroid.app.posts.fromHtml
import org.pixeldroid.app.utils.BaseFragment import org.pixeldroid.app.utils.BaseFragment
import java.io.File import java.io.File
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
@ -326,7 +327,7 @@ class CameraFragment : BaseFragment() {
} }
private fun setupUploadImage() { private fun setupUploadImage() {
val videoEnabled: Boolean = db.instanceDao().getInstance(db.userDao().getActiveUser()!!.instance_uri).videoEnabled val videoEnabled: Boolean = db.instanceDao().getActiveInstance().videoEnabled
var mimeTypes: Array<String> = arrayOf("image/*") var mimeTypes: Array<String> = arrayOf("image/*")
if(videoEnabled) mimeTypes += "video/*" if(videoEnabled) mimeTypes += "video/*"
@ -449,21 +450,16 @@ class CameraFragment : BaseFragment() {
private fun startAlbumCreation(uris: ArrayList<String>) { private fun startAlbumCreation(uris: ArrayList<String>) {
val intent = Intent(requireActivity(), PostCreationActivity::class.java) val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
.apply { // Pass downloaded images to new post creation activity
uris.forEach{ putParcelableArrayListExtra(
//Why are we using ClipData here? Because the FLAG_GRANT_READ_URI_PERMISSION Intent.EXTRA_STREAM, ArrayList(uris.map { it.toUri() })
//needs to be applied to the URIs, and this flag only applies to the )
//Intent's data and any URIs specified in its ClipData. setClass(requireContext(), PostCreationActivity::class.java)
if(clipData == null){
clipData = ClipData("", emptyArray(), ClipData.Item(it.toUri())) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else { addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
clipData!!.addItem(ClipData.Item(it.toUri())) }
}
}
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if(inActivity && !addToStory){ if(inActivity && !addToStory){
requireActivity().setResult(Activity.RESULT_OK, intent) requireActivity().setResult(Activity.RESULT_OK, intent)

View File

@ -2,10 +2,8 @@ package org.pixeldroid.app.posts
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_DENIED import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -77,7 +75,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
fun bind( fun bind(
status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase, status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>,
requestPermissionDownloadPic: ActivityResultLauncher<String>, isActivity: Boolean = false requestPermissionDownloadPic: ActivityResultLauncher<String>, isActivity: Boolean = false,
) { ) {
this.itemView.visibility = View.VISIBLE this.itemView.visibility = View.VISIBLE
@ -371,7 +369,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
apiHolder: PixelfedAPIHolder, apiHolder: PixelfedAPIHolder,
db: AppDatabase, db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
requestPermissionDownloadPic: ActivityResultLauncher<String> requestPermissionDownloadPic: ActivityResultLauncher<String>,
){ ){
var bookmarked: Boolean? = null var bookmarked: Boolean? = null
binding.statusMore.setOnClickListener { binding.statusMore.setOnClickListener {
@ -449,178 +447,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
true true
} }
R.id.post_more_menu_redraft -> { R.id.post_more_menu_redraft -> launchRedraftDialog(lifecycleScope, apiHolder, db)
MaterialAlertDialogBuilder(binding.root.context).apply {
setMessage(R.string.redraft_dialog_launch)
setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch {
try {
// Create new post creation activity
val intent =
Intent(context, PostCreationActivity::class.java)
// Get descriptions and images from original post
val postDescription = status?.content ?: ""
val postAttachments =
status?.media_attachments!! // Catch possible exception from !! (?)
val postNSFW = status?.sensitive
val imageUriStrings = postAttachments.map { postAttachment ->
postAttachment.url ?: ""
}
val imageNames = imageUriStrings.map { imageUriString ->
Uri.parse(imageUriString).lastPathSegment.toString()
}
val downloadedFiles = imageNames.map { imageName ->
File(context.cacheDir, imageName)
}
val imageUris = downloadedFiles.map { downloadedFile ->
Uri.fromFile(downloadedFile)
}
val imageDescriptions = postAttachments.map { postAttachment ->
fromHtml(postAttachment.description ?: "").toString()
}
val downloadRequests: List<Request> = imageUriStrings.map { imageUriString ->
Request.Builder().url(imageUriString).build()
}
val counter = AtomicInteger(0)
// Define callback function for after downloading the images
fun continuation() {
// Wait for all outstanding downloads to finish
if (counter.incrementAndGet() == imageUris.size) {
if (allFilesExist(imageNames)) {
// Delete original post
lifecycleScope.launch {
deletePost(apiHolder.api ?: apiHolder.setToCurrentUser(), db)
}
val counterInt = counter.get()
Toast.makeText(
binding.root.context,
binding.root.context.resources.getQuantityString(
R.plurals.items_load_success,
counterInt,
counterInt
),
Toast.LENGTH_SHORT
).show()
// Pass downloaded images to new post creation activity
intent.apply {
imageUris.zip(imageDescriptions).map { (imageUri, imageDescription) ->
ClipData.Item(imageDescription, null, imageUri)
}.forEach { imageItem ->
if (clipData == null) {
clipData = ClipData(
"",
emptyArray(),
imageItem
)
} else {
clipData!!.addItem(imageItem)
}
}
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
// Pass post description of existing post to new post creation activity
intent.putExtra(
PostCreationActivity.PICTURE_DESCRIPTION,
fromHtml(postDescription).toString()
)
if (imageNames.isNotEmpty()) {
intent.putExtra(
PostCreationActivity.TEMP_FILES,
imageNames.toTypedArray()
)
}
intent.putExtra(
PostCreationActivity.POST_REDRAFT,
true
)
intent.putExtra(
PostCreationActivity.POST_NSFW,
postNSFW
)
// Launch post creation activity
binding.root.context.startActivity(intent)
}
}
}
if (!allFilesExist(imageNames)) {
// Track download progress
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.image_download_downloading),
Toast.LENGTH_SHORT
).show()
}
// Iterate through all pictures of the original post
downloadRequests.zip(downloadedFiles).forEach { (downloadRequest, downloadedFile) ->
// Check whether image is in cache directory already (maybe rather do so using Glide in the future?)
if (!downloadedFile.exists()) {
OkHttpClient().newCall(downloadRequest)
.enqueue(object : Callback {
override fun onFailure(
call: Call,
e: IOException
) {
Looper.prepare()
downloadedFile.delete()
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.redraft_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
@Throws(IOException::class)
override fun onResponse(
call: Call,
response: Response
) {
val sink: BufferedSink =
downloadedFile.sink().buffer()
sink.writeAll(response.body!!.source())
sink.close()
Looper.prepare()
continuation()
}
})
} else {
continuation()
}
}
} catch (exception: HttpException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(
R.string.redraft_post_failed_error,
exception.code()
),
Toast.LENGTH_SHORT
).show()
} catch (exception: IOException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.redraft_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
}
}
setNegativeButton(android.R.string.cancel) { _, _ -> }
show()
}
true
}
else -> false else -> false
} }
} }
@ -645,6 +472,176 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private fun launchRedraftDialog(
lifecycleScope: LifecycleCoroutineScope,
apiHolder: PixelfedAPIHolder,
db: AppDatabase
): Boolean {
MaterialAlertDialogBuilder(binding.root.context).apply {
setMessage(R.string.redraft_dialog_launch)
setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch {
try {
// Get descriptions and images from original post
val postDescription = status?.content ?: ""
val postAttachments =
status?.media_attachments!! // TODO Catch possible exception from !! (?)
val postNSFW = status?.sensitive
val imageUriStrings = postAttachments.map { postAttachment ->
postAttachment.url ?: ""
}
val imageNames = imageUriStrings.map { imageUriString ->
Uri.parse(imageUriString).lastPathSegment.toString()
}
val downloadedFiles = imageNames.map { imageName ->
File(context.cacheDir, imageName)
}
val imageDescriptions = postAttachments.map { postAttachment ->
fromHtml(
postAttachment.description ?: ""
).toString()
}
val downloadRequests: List<Request> =
imageUriStrings.map { imageUriString ->
Request.Builder().url(imageUriString).build()
}
val imageUris = downloadedFiles.map { downloadedFile ->
Uri.fromFile(downloadedFile)
}
val counter = AtomicInteger(0)
// Define callback function for after downloading the images
fun continuation() {
// Wait for all outstanding downloads to finish
if (counter.incrementAndGet() == imageUris.size) {
if (allFilesExist(imageNames)) {
// Delete original post
lifecycleScope.launch {
deletePost(
apiHolder.api ?: apiHolder.setToCurrentUser(), db
)
}
val counterInt = counter.get()
Toast.makeText(
binding.root.context,
binding.root.context.resources.getQuantityString(
R.plurals.items_load_success, counterInt, counterInt
),
Toast.LENGTH_SHORT
).show()
// Create new post creation activity
//TODO use this instead of clipdata (everywhere)
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
// Pass downloaded images to new post creation activity
putParcelableArrayListExtra(
Intent.EXTRA_STREAM, ArrayList(imageUris)
)
setClass(context, PostCreationActivity::class.java)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
putExtra(
PostCreationActivity.PICTURE_DESCRIPTIONS,
ArrayList(imageDescriptions)
)
// Pass post description of existing post to new post creation activity
putExtra(
PostCreationActivity.POST_DESCRIPTION,
fromHtml(postDescription).toString()
)
if (imageNames.isNotEmpty()) {
putExtra(
PostCreationActivity.TEMP_FILES,
imageNames.toTypedArray()
)
}
putExtra(PostCreationActivity.POST_REDRAFT, true)
putExtra(PostCreationActivity.POST_NSFW, postNSFW)
}
// Launch post creation activity
binding.root.context.startActivity(intent)
}
}
}
if (!allFilesExist(imageNames)) {
// Track download progress
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.image_download_downloading),
Toast.LENGTH_SHORT
).show()
}
// Iterate through all pictures of the original post
downloadRequests.zip(downloadedFiles)
.forEach { (downloadRequest, downloadedFile) ->
// Check whether image is in cache directory already (maybe rather do so using Glide in the future?)
if (!downloadedFile.exists()) {
OkHttpClient().newCall(downloadRequest)
.enqueue(object : Callback {
override fun onFailure(
call: Call,
e: IOException,
) {
Looper.prepare()
downloadedFile.delete()
Toast.makeText(
binding.root.context,
binding.root.context.getString(
R.string.redraft_post_failed_io_except
),
Toast.LENGTH_SHORT
).show()
}
@Throws(IOException::class)
override fun onResponse(
call: Call,
response: Response,
) {
val sink: BufferedSink =
downloadedFile.sink().buffer()
sink.writeAll(response.body!!.source())
sink.close()
Looper.prepare()
continuation()
}
})
} else {
continuation()
}
}
} catch (exception: HttpException) {
Toast.makeText(
binding.root.context, binding.root.context.getString(
R.string.redraft_post_failed_error, exception.code()
), Toast.LENGTH_SHORT
).show()
} catch (exception: IOException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.redraft_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
}
}
setNegativeButton(android.R.string.cancel) { _, _ -> }
show()
}
return true
}
private fun activateLiker( private fun activateLiker(
apiHolder: PixelfedAPIHolder, apiHolder: PixelfedAPIHolder,
isLiked: Boolean, isLiked: Boolean,

View File

@ -16,18 +16,20 @@
package org.pixeldroid.app.posts.feeds.cachedFeeds package org.pixeldroid.app.posts.feeds.cachedFeeds
import androidx.paging.* import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.RemoteMediator
import kotlinx.coroutines.flow.Flow
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao import org.pixeldroid.app.utils.db.dao.feedContent.FeedContentDao
import org.pixeldroid.app.utils.api.objects.FeedContentDatabase
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
/** /**
* Repository class that works with local and remote data sources. * Repository class that works with local and remote data sources.
*/ */
class FeedContentRepository<T: FeedContentDatabase> @ExperimentalPagingApi class FeedContentRepository<T: FeedContentDatabase> @ExperimentalPagingApi constructor(
@Inject constructor(
private val db: AppDatabase, private val db: AppDatabase,
private val dao: FeedContentDao<T>, private val dao: FeedContentDao<T>,
private val mediator: RemoteMediator<Int, T> private val mediator: RemoteMediator<Int, T>

View File

@ -1,12 +1,14 @@
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
import androidx.paging.* import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction import androidx.room.withTransaction
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
import java.lang.NullPointerException import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import javax.inject.Inject
/** /**
@ -17,7 +19,7 @@ import javax.inject.Inject
* a local db cache. * a local db cache.
*/ */
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
class HomeFeedRemoteMediator @Inject constructor( class HomeFeedRemoteMediator(
private val apiHolder: PixelfedAPIHolder, private val apiHolder: PixelfedAPIHolder,
private val db: AppDatabase, private val db: AppDatabase,
) : RemoteMediator<Int, HomeStatusDatabaseEntity>() { ) : RemoteMediator<Int, HomeStatusDatabaseEntity>() {

View File

@ -16,13 +16,15 @@
package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds package org.pixeldroid.app.posts.feeds.cachedFeeds.postFeeds
import androidx.paging.* import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction import androidx.room.withTransaction
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import java.lang.NullPointerException
import javax.inject.Inject
/** /**
* RemoteMediator for the public feed. * RemoteMediator for the public feed.
@ -32,7 +34,7 @@ import javax.inject.Inject
* a local db cache. * a local db cache.
*/ */
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
class PublicFeedRemoteMediator @Inject constructor( class PublicFeedRemoteMediator(
private val apiHolder: PixelfedAPIHolder, private val apiHolder: PixelfedAPIHolder,
private val db: AppDatabase private val db: AppDatabase
) : RemoteMediator<Int, PublicFeedStatusDatabaseEntity>() { ) : RemoteMediator<Int, PublicFeedStatusDatabaseEntity>() {

View File

@ -25,7 +25,7 @@ import org.pixeldroid.app.utils.openUrl
class EditProfileActivity : BaseActivity() { class EditProfileActivity : BaseActivity() {
private lateinit var model: EditProfileViewModel private val model: EditProfileViewModel by viewModels()
private lateinit var binding: ActivityEditProfileBinding private lateinit var binding: ActivityEditProfileBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -35,9 +35,6 @@ class EditProfileActivity : BaseActivity() {
setSupportActionBar(binding.topBar) setSupportActionBar(binding.topBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val _model: EditProfileViewModel by viewModels { EditProfileViewModelFactory(application) }
model = _model
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
// Handle the back button event // Handle the back button event
if(model.madeChanges()){ if(model.madeChanges()){

View File

@ -1,16 +1,16 @@
package org.pixeldroid.app.profile package org.pixeldroid.app.profile
import android.app.Application import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.text.Editable import android.text.Editable
import android.util.Log import android.util.Log
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
@ -21,7 +21,6 @@ import kotlinx.coroutines.launch
import okhttp3.MultipartBody import okhttp3.MultipartBody
import org.pixeldroid.app.postCreation.ProgressRequestBody import org.pixeldroid.app.postCreation.ProgressRequestBody
import org.pixeldroid.app.posts.fromHtml import org.pixeldroid.app.posts.fromHtml
import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.updateUserInfoDb import org.pixeldroid.app.utils.db.updateUserInfoDb
@ -29,7 +28,10 @@ import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import retrofit2.HttpException import retrofit2.HttpException
import javax.inject.Inject import javax.inject.Inject
class EditProfileViewModel(application: Application) : AndroidViewModel(application) { @HiltViewModel
class EditProfileViewModel @Inject constructor(
@ApplicationContext private val applicationContext: Context
): ViewModel() {
@Inject @Inject
lateinit var apiHolder: PixelfedAPIHolder lateinit var apiHolder: PixelfedAPIHolder
@ -46,7 +48,6 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
private set private set
init { init {
(application as PixelDroidApplication).getAppComponent().inject(this)
loadProfile() loadProfile()
} }
@ -197,12 +198,12 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
val image = uiState.value.profilePictureUri!! val image = uiState.value.profilePictureUri!!
val inputStream = val inputStream =
getApplication<PixelDroidApplication>().contentResolver.openInputStream(image) applicationContext.contentResolver.openInputStream(image)
?: return ?: return
val size: Long = val size: Long =
if (image.scheme == "content") { if (image.scheme == "content") {
getApplication<PixelDroidApplication>().contentResolver.query( applicationContext.contentResolver.query(
image, image,
null, null,
null, null,
@ -303,10 +304,4 @@ data class EditProfileActivityUiState(
val error: Boolean = false, val error: Boolean = false,
val uploadingPicture: Boolean = false, val uploadingPicture: Boolean = false,
val uploadProgress: Int = 0, val uploadProgress: Int = 0,
) )
class EditProfileViewModelFactory(val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Application::class.java).newInstance(application)
}
}

View File

@ -25,9 +25,6 @@ import org.pixeldroid.app.databinding.ActivityStoriesBinding
import org.pixeldroid.app.posts.setTextViewFromISO8601 import org.pixeldroid.app.posts.setTextViewFromISO8601
import org.pixeldroid.app.utils.BaseActivity import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.api.objects.Account import org.pixeldroid.app.utils.api.objects.Account
import org.pixeldroid.app.utils.api.objects.Story
import org.pixeldroid.app.utils.api.objects.StoryCarousel
class StoriesActivity: BaseActivity() { class StoriesActivity: BaseActivity() {
@ -37,12 +34,11 @@ class StoriesActivity: BaseActivity() {
const val STORY_CAROUSEL_USER_ID = "LaunchStoryUserId" const val STORY_CAROUSEL_USER_ID = "LaunchStoryUserId"
} }
private lateinit var binding: ActivityStoriesBinding private lateinit var binding: ActivityStoriesBinding
private lateinit var storyProgress: StoryProgress private lateinit var storyProgress: StoryProgress
private lateinit var model: StoriesViewModel private val model: StoriesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
//force night mode always //force night mode always
@ -50,18 +46,9 @@ class StoriesActivity: BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val carousel = intent.getSerializableExtra(STORY_CAROUSEL) as? StoryCarousel
val userId = intent.getStringExtra(STORY_CAROUSEL_USER_ID)
val selfCarousel: Array<Story>? = intent.getSerializableExtra(STORY_CAROUSEL_SELF) as? Array<Story>
binding = ActivityStoriesBinding.inflate(layoutInflater) binding = ActivityStoriesBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
val _model: StoriesViewModel by viewModels {
StoriesViewModelFactory(application, carousel, userId, selfCarousel?.asList())
}
model = _model
storyProgress = StoryProgress(model.uiState.value.imageList.size) storyProgress = StoryProgress(model.uiState.value.imageList.size)
binding.storyProgressImage.setImageDrawable(storyProgress) binding.storyProgressImage.setImageDrawable(storyProgress)

View File

@ -1,20 +1,18 @@
package org.pixeldroid.app.stories package org.pixeldroid.app.stories
import android.app.Application
import android.os.CountDownTimer import android.os.CountDownTimer
import android.text.Editable import android.text.Editable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.api.objects.CarouselUserContainer import org.pixeldroid.app.utils.api.objects.CarouselUserContainer
import org.pixeldroid.app.utils.api.objects.Story import org.pixeldroid.app.utils.api.objects.Story
import org.pixeldroid.app.utils.api.objects.StoryCarousel import org.pixeldroid.app.utils.api.objects.StoryCarousel
@ -37,18 +35,13 @@ data class StoriesUiState(
val snackBar: Int? = null, val snackBar: Int? = null,
val reply: String = "" val reply: String = ""
) )
@HiltViewModel
class StoriesViewModel( class StoriesViewModel @Inject constructor(state: SavedStateHandle,
application: Application, db: AppDatabase,
val carousel: StoryCarousel?, private val apiHolder: PixelfedAPIHolder) : ViewModel() {
userId: String?, private val carousel: StoryCarousel? = state[StoriesActivity.STORY_CAROUSEL]
val selfCarousel: List<Story>? private val userId: String? = state[StoriesActivity.STORY_CAROUSEL_USER_ID]
) : AndroidViewModel(application) { private val selfCarousel: Array<Story>? = state[StoriesActivity.STORY_CAROUSEL_SELF]
@Inject
lateinit var apiHolder: PixelfedAPIHolder
@Inject
lateinit var db: AppDatabase
private var currentAccount: CarouselUserContainer? private var currentAccount: CarouselUserContainer?
@ -61,10 +54,9 @@ class StoriesViewModel(
private var timer: CountDownTimer? = null private var timer: CountDownTimer? = null
init { init {
(application as PixelDroidApplication).getAppComponent().inject(this)
currentAccount = currentAccount =
if (selfCarousel != null) { if (selfCarousel != null) {
db.userDao().getActiveUser()?.let { CarouselUserContainer(it, selfCarousel) } db.userDao().getActiveUser()?.let { CarouselUserContainer(it, selfCarousel.toList()) }
} else carousel?.nodes?.firstOrNull { it?.user?.id == userId } } else carousel?.nodes?.firstOrNull { it?.user?.id == userId }
_uiState = MutableStateFlow(newUiStateFromCurrentAccount()) _uiState = MutableStateFlow(newUiStateFromCurrentAccount())
@ -216,14 +208,3 @@ class StoriesViewModel(
fun currentProfileId(): String? = currentAccount?.user?.id fun currentProfileId(): String? = currentAccount?.user?.id
} }
class StoriesViewModelFactory(
val application: Application,
val carousel: StoryCarousel?,
val userId: String?,
val selfCarousel: List<Story>?
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Application::class.java, StoryCarousel::class.java, String::class.java, List::class.java).newInstance(application, carousel, userId, selfCarousel)
}
}

View File

@ -1,10 +1,11 @@
package org.pixeldroid.app.utils package org.pixeldroid.app.utils
import android.os.Bundle import dagger.hilt.android.AndroidEntryPoint
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint
open class BaseActivity : org.pixeldroid.common.ThemedActivity() { open class BaseActivity : org.pixeldroid.common.ThemedActivity() {
@Inject @Inject
@ -12,11 +13,6 @@ open class BaseActivity : org.pixeldroid.common.ThemedActivity() {
@Inject @Inject
lateinit var apiHolder: PixelfedAPIHolder lateinit var apiHolder: PixelfedAPIHolder
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(this.application as PixelDroidApplication).getAppComponent().inject(this)
}
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
return true return true

View File

@ -1,9 +1,9 @@
package org.pixeldroid.app.utils package org.pixeldroid.app.utils
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
@ -12,6 +12,7 @@ import javax.inject.Inject
/** /**
* Base Fragment, for dependency injection and other things common to a lot of the fragments * Base Fragment, for dependency injection and other things common to a lot of the fragments
*/ */
@AndroidEntryPoint
open class BaseFragment: Fragment() { open class BaseFragment: Fragment() {
@Inject @Inject
@ -20,11 +21,6 @@ open class BaseFragment: Fragment() {
@Inject @Inject
lateinit var db: AppDatabase lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(requireActivity().application as PixelDroidApplication).getAppComponent().inject(this)
}
internal val requestPermissionDownloadPic = internal val requestPermissionDownloadPic =
registerForActivityResult( registerForActivityResult(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()

View File

@ -3,14 +3,12 @@ package org.pixeldroid.app.utils
import android.app.Application import android.app.Application
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import org.ligi.tracedroid.TraceDroid import org.ligi.tracedroid.TraceDroid
import org.pixeldroid.app.utils.di.*
@HiltAndroidApp
class PixelDroidApplication: Application() { class PixelDroidApplication: Application() {
private lateinit var mApplicationComponent: ApplicationComponent
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -19,18 +17,7 @@ class PixelDroidApplication: Application() {
val sharedPreferences = val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
setThemeFromPreferences(sharedPreferences, resources) setThemeFromPreferences(sharedPreferences, resources)
mApplicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.databaseModule(DatabaseModule(applicationContext))
.aPIModule(APIModule())
.build()
mApplicationComponent.inject(this)
DynamicColors.applyToActivitiesIfAvailable(this) DynamicColors.applyToActivitiesIfAvailable(this)
} }
fun getAppComponent(): ApplicationComponent {
return mApplicationComponent
}
} }

View File

@ -44,7 +44,7 @@ suspend fun updateUserInfoDb(db: AppDatabase, account: Account) {
) )
} }
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) { suspend fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run { val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
InstanceDatabaseEntity( InstanceDatabaseEntity(
uri = normalizeDomain(metadata?.config?.site?.url!!), uri = normalizeDomain(metadata?.config?.site?.url!!),

View File

@ -1,13 +1,15 @@
package org.pixeldroid.app.utils.db.dao package org.pixeldroid.app.utils.db.dao
import androidx.room.* import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity
@Dao @Dao
interface InstanceDao { interface InstanceDao {
@Query("SELECT * FROM instances")
fun getAll(): List<InstanceDatabaseEntity>
@Query("SELECT * FROM instances WHERE uri=:instanceUri") @Query("SELECT * FROM instances WHERE uri=:instanceUri")
fun getInstance(instanceUri: String): InstanceDatabaseEntity fun getInstance(instanceUri: String): InstanceDatabaseEntity
@ -19,13 +21,13 @@ interface InstanceDao {
* Insert an instance, if it already exists return -1 * Insert an instance, if it already exists return -1
*/ */
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertInstance(instance: InstanceDatabaseEntity): Long suspend fun insertInstance(instance: InstanceDatabaseEntity): Long
@Update @Update
fun updateInstance(instance: InstanceDatabaseEntity) suspend fun updateInstance(instance: InstanceDatabaseEntity)
@Transaction @Transaction
fun insertOrUpdate(instance: InstanceDatabaseEntity) { suspend fun insertOrUpdate(instance: InstanceDatabaseEntity) {
if (insertInstance(instance) == -1L) { if (insertInstance(instance) == -1L) {
updateInstance(instance) updateInstance(instance)
} }

View File

@ -1,6 +1,11 @@
package org.pixeldroid.app.utils.db.dao package org.pixeldroid.app.utils.db.dao
import androidx.room.* import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity

View File

@ -6,13 +6,16 @@ import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.* import okhttp3.*
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
class APIModule{ @InstallIn(SingletonComponent::class)
class APIModule {
@Provides @Provides
@Singleton @Singleton
@ -54,7 +57,7 @@ class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase, val
client_secret = user.clientSecret client_secret = user.clientSecret
) )
} }
}catch (e: Exception){ } catch (e: Exception){
return null return null
} }

View File

@ -1,34 +0,0 @@
package org.pixeldroid.app.utils.di
import android.app.Application
import android.content.Context
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.BaseFragment
import dagger.Component
import org.pixeldroid.app.MainActivityViewModel
import org.pixeldroid.app.postCreation.PostCreationViewModel
import org.pixeldroid.app.profile.EditProfileViewModel
import org.pixeldroid.app.stories.StoriesViewModel
import org.pixeldroid.app.stories.StoryCarouselViewHolder
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
import javax.inject.Singleton
@Singleton
@Component(modules = [ApplicationModule::class, DatabaseModule::class, APIModule::class])
interface ApplicationComponent {
fun inject(application: PixelDroidApplication?)
fun inject(activity: BaseActivity?)
fun inject(feedFragment: BaseFragment)
fun inject(notificationsWorker: NotificationsWorker)
fun inject(postCreationViewModel: PostCreationViewModel)
fun inject(editProfileViewModel: EditProfileViewModel)
fun inject(storiesViewModel: StoriesViewModel)
fun inject(mainActivityViewModel: MainActivityViewModel)
val context: Context?
val application: Application?
val database: AppDatabase
}

View File

@ -1,27 +0,0 @@
package org.pixeldroid.app.utils.di
import android.app.Application
import android.content.Context
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class ApplicationModule(app: Application) {
private val mApplication: Application = app
@Singleton
@Provides
fun provideContext(): Context {
return mApplication
}
@Singleton
@Provides
fun provideApplication(): Application {
return mApplication
}
}

View File

@ -5,19 +5,25 @@ import androidx.room.Room
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.pixeldroid.app.utils.db.MIGRATION_3_4 import org.pixeldroid.app.utils.db.MIGRATION_3_4
import org.pixeldroid.app.utils.db.MIGRATION_4_5 import org.pixeldroid.app.utils.db.MIGRATION_4_5
import org.pixeldroid.app.utils.db.MIGRATION_5_6 import org.pixeldroid.app.utils.db.MIGRATION_5_6
import javax.inject.Singleton import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module @Module
class DatabaseModule(private val context: Context) { class DatabaseModule {
@Provides @Provides
@Singleton @Singleton
fun providesDatabase(): AppDatabase { fun providesDatabase(
@ApplicationContext applicationContext: Context
): AppDatabase {
return Room.databaseBuilder( return Room.databaseBuilder(
context, applicationContext,
AppDatabase::class.java, "pixeldroid" AppDatabase::class.java, "pixeldroid"
).addMigrations(MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6) ).addMigrations(MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
.allowMainThreadQueries().build() .allowMainThreadQueries().build()

View File

@ -32,9 +32,6 @@ import java.io.IOException
import java.time.Instant import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
class NotificationsWorker( class NotificationsWorker(
context: Context, context: Context,
params: WorkerParameters params: WorkerParameters
@ -46,9 +43,6 @@ class NotificationsWorker(
lateinit var apiHolder: PixelfedAPIHolder lateinit var apiHolder: PixelfedAPIHolder
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
(applicationContext as PixelDroidApplication).getAppComponent().inject(this)
val users: List<UserDatabaseEntity> = db.userDao().getAll() val users: List<UserDatabaseEntity> = db.userDao().getAll()
for (user in users){ for (user in users){
@ -306,8 +300,7 @@ fun removeNotificationChannelsFromAccount(context: Context, user: UserDatabaseEn
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
notificationManager.deleteNotificationChannelGroup(channelGroupId.hashCode().toString()) notificationManager.deleteNotificationChannelGroup(channelGroupId.hashCode().toString())
} else { } else {
val types: MutableList<Notification.NotificationType?> = val types: MutableList<Notification.NotificationType?> = entries.toMutableList()
Notification.NotificationType.values().toMutableList()
types += null types += null
types.forEach { types.forEach {

View File

@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.2.1' classpath 'com.android.tools.build:gradle:8.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@ -16,6 +16,7 @@ buildscript {
plugins { plugins {
id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false
id("com.google.dagger.hilt.android") version "2.50" apply false
} }
allprojects { allprojects {

View File

@ -1,7 +1,7 @@
#Fri Oct 14 13:37:44 GMT 2022 #Fri Oct 14 13:37:44 GMT 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3 distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists