configure ktlint gradle plugin

This commit is contained in:
Konrad Pozniak 2020-06-12 19:58:15 +02:00
parent 93a31af6a6
commit 0e8ad1e9ed
77 changed files with 517 additions and 550 deletions

View File

@ -35,7 +35,6 @@ android {
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
} }
} }
android.sourceSets["main"].java.srcDir("src/main/kotlin") android.sourceSets["main"].java.srcDir("src/main/kotlin")
@ -47,6 +46,11 @@ tasks {
} }
} }
ktlint {
version.set("0.37.1")
disabledRules.set(setOf("import-ordering"))
}
dependencies { dependencies {
val lifecycleVersion = "2.3.0-alpha04" val lifecycleVersion = "2.3.0-alpha04"
@ -94,7 +98,7 @@ dependencies {
implementation("com.google.dagger:dagger:2.28") implementation("com.google.dagger:dagger:2.28")
implementation("com.fxn769:pix:1.4.4") implementation("com.fxn769:pix:1.4.4")
implementation( "com.github.yalantis:ucrop:2.2.5") implementation("com.github.yalantis:ucrop:2.2.5")
implementation("me.relex:circleindicator:2.1.4") implementation("me.relex:circleindicator:2.1.4")

View File

@ -34,5 +34,4 @@ class PixelcatApplication : DaggerApplication() {
.application(this) .application(this)
.build() .build()
} }
} }

View File

@ -40,10 +40,9 @@ class AboutActivity : BaseActivity() {
binding.aboutLicensesButton.setOnClickListener { binding.aboutLicensesButton.setOnClickListener {
startActivity(LicenseActivity.newIntent(this)) startActivity(LicenseActivity.newIntent(this))
} }
} }
companion object { companion object {
fun newIntent(context: Context) = Intent(context, AboutActivity::class.java) fun newIntent(context: Context) = Intent(context, AboutActivity::class.java)
} }
} }

View File

@ -38,7 +38,6 @@ class LicenseActivity : BaseActivity() {
} }
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView) loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
} }
private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) { private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) {
@ -61,10 +60,9 @@ class LicenseActivity : BaseActivity() {
br.close() br.close()
textView.text = sb.toString() textView.text = sb.toString()
} }
companion object { companion object {
fun newIntent(context: Context) = Intent(context, LicenseActivity::class.java) fun newIntent(context: Context) = Intent(context, LicenseActivity::class.java)
} }
} }

View File

@ -10,9 +10,9 @@ import com.google.android.material.card.MaterialCardView
class LicenseCard class LicenseCard
@JvmOverloads constructor( @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : MaterialCardView(context, attrs, defStyleAttr) { ) : MaterialCardView(context, attrs, defStyleAttr) {
private val binding = private val binding =
@ -31,14 +31,11 @@ class LicenseCard
binding.licenseCardName.text = name binding.licenseCardName.text = name
binding.licenseCardLicense.text = license binding.licenseCardLicense.text = license
if(link.isNullOrBlank()) { if (link.isNullOrBlank()) {
binding.licenseCardLink.hide() binding.licenseCardLink.hide()
} else { } else {
binding.licenseCardLink.text = link binding.licenseCardLink.text = link
// setOnClickListener { LinkHelper.openLink(link, context) } // setOnClickListener { LinkHelper.openLink(link, context) }
} }
} }
} }

View File

@ -52,9 +52,7 @@ class AccountSelectionAdapter(
} }
} }
} }
} }
class AccountSelectionViewHolder(val binding: ItemAccountSelectionBinding) : class AccountSelectionViewHolder(val binding: ItemAccountSelectionBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)

View File

@ -13,7 +13,6 @@ import at.connyduck.pixelcat.db.AccountManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AccountSelectionBottomSheet( class AccountSelectionBottomSheet(
private val accountManager: AccountManager private val accountManager: AccountManager
) : BottomSheetDialogFragment() { ) : BottomSheetDialogFragment() {
@ -22,7 +21,6 @@ class AccountSelectionBottomSheet(
private val binding private val binding
get() = _binding!! get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -50,7 +48,7 @@ class AccountSelectionBottomSheet(
} }
private fun onNewAccount() { private fun onNewAccount() {
//TODO don't create intent here // TODO don't create intent here
startActivity(Intent(requireContext(), LoginActivity::class.java)) startActivity(Intent(requireContext(), LoginActivity::class.java))
} }
@ -58,5 +56,4 @@ class AccountSelectionBottomSheet(
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
}
}

View File

@ -34,13 +34,10 @@ class MenuBottomSheet : BottomSheetDialogFragment() {
startActivity(AboutActivity.newIntent(it.context)) startActivity(AboutActivity.newIntent(it.context))
dismiss() dismiss()
} }
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
}
}

View File

@ -18,7 +18,7 @@ import com.fxn.pix.Pix
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import javax.inject.Inject import javax.inject.Inject
class ComposeActivity: BaseActivity() { class ComposeActivity : BaseActivity() {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory
@ -35,7 +35,6 @@ class ComposeActivity: BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
binding.root.setOnApplyWindowInsetsListener { _, insets -> binding.root.setOnApplyWindowInsetsListener { _, insets ->
val top = insets.systemWindowInsetTop val top = insets.systemWindowInsetTop
@ -45,11 +44,10 @@ class ComposeActivity: BaseActivity() {
insets.consumeSystemWindowInsets() insets.consumeSystemWindowInsets()
} }
if(viewModel.images.value.isNullOrEmpty()) { if (viewModel.images.value.isNullOrEmpty()) {
viewModel.addImage(intent.getStringExtra(EXTRA_MEDIA_URI)!!) viewModel.addImage(intent.getStringExtra(EXTRA_MEDIA_URI)!!)
} }
setSupportActionBar(binding.composeToolBar) setSupportActionBar(binding.composeToolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -75,23 +73,27 @@ class ComposeActivity: BaseActivity() {
changeVisibility(VISIBILITY.FOLLOWERS_ONLY) changeVisibility(VISIBILITY.FOLLOWERS_ONLY)
} }
viewModel.images.observe(this, Observer { viewModel.images.observe(
adapter.submitList(it) this,
}) Observer {
adapter.submitList(it)
viewModel.visibility.observe(this, Observer {
val visibilityString = when(it) {
VISIBILITY.PUBLIC -> R.string.compose_visibility_public
VISIBILITY.UNLISTED -> R.string.compose_visibility_unlisted
VISIBILITY.FOLLOWERS_ONLY -> R.string.compose_visibility_followers_only
} }
)
binding.composeVisibilityButton.text = getString(R.string.compose_visibility, getString(visibilityString)) viewModel.visibility.observe(
this,
Observer {
val visibilityString = when (it) {
VISIBILITY.PUBLIC -> R.string.compose_visibility_public
VISIBILITY.UNLISTED -> R.string.compose_visibility_unlisted
VISIBILITY.FOLLOWERS_ONLY -> R.string.compose_visibility_followers_only
}
}) binding.composeVisibilityButton.text = getString(R.string.compose_visibility, getString(visibilityString))
}
)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_PICK_MEDIA) { if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_PICK_MEDIA) {
@ -99,7 +101,6 @@ class ComposeActivity: BaseActivity() {
data?.getStringArrayListExtra(Pix.IMAGE_RESULTS) data?.getStringArrayListExtra(Pix.IMAGE_RESULTS)
Log.e("Result", returnValue.toString()) Log.e("Result", returnValue.toString())
viewModel.addImage(returnValue?.first()!!) viewModel.addImage(returnValue?.first()!!)
} }
} }
@ -108,7 +109,6 @@ class ComposeActivity: BaseActivity() {
visibilityBottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED visibilityBottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
} }
companion object { companion object {
private const val REQUEST_CODE_PICK_MEDIA = 123 private const val REQUEST_CODE_PICK_MEDIA = 123
private const val EXTRA_MEDIA_URI = "MEDIA_URI" private const val EXTRA_MEDIA_URI = "MEDIA_URI"
@ -119,4 +119,4 @@ class ComposeActivity: BaseActivity() {
} }
} }
} }
} }

View File

@ -41,4 +41,4 @@ class ComposeImageAdapter : ListAdapter<String, ComposeImageViewHolder>(
} }
class ComposeImageViewHolder(val binding: ItemComposeImageBinding) : class ComposeImageViewHolder(val binding: ItemComposeImageBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)

View File

@ -12,23 +12,17 @@ import javax.inject.Inject
class ComposeViewModel @Inject constructor( class ComposeViewModel @Inject constructor(
val context: Context, val context: Context,
val accountManager: AccountManager val accountManager: AccountManager
): ViewModel() { ) : ViewModel() {
val images = MutableLiveData<List<String>>() val images = MutableLiveData<List<String>>()
val visibility = MutableLiveData(VISIBILITY.PUBLIC) val visibility = MutableLiveData(VISIBILITY.PUBLIC)
fun addImage(imageUri: String) { fun addImage(imageUri: String) {
images.value = images.value.orEmpty() + imageUri images.value = images.value.orEmpty() + imageUri
} }
fun setVisibility(visibility: VISIBILITY) { fun setVisibility(visibility: VISIBILITY) {
this.visibility.value = visibility this.visibility.value = visibility
} }
@ -47,14 +41,11 @@ class ComposeViewModel @Inject constructor(
val intent = SendStatusService.sendStatusIntent(context, statusToSend) val intent = SendStatusService.sendStatusIntent(context, statusToSend)
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
} }
} }
enum class VISIBILITY(val serverName: String) { enum class VISIBILITY(val serverName: String) {
PUBLIC("public"), PUBLIC("public"),
UNLISTED("unlisted"), UNLISTED("unlisted"),
FOLLOWERS_ONLY("private") FOLLOWERS_ONLY("private")
} }

View File

@ -19,18 +19,24 @@ import at.connyduck.pixelcat.network.FediverseApi
import at.connyduck.pixelcat.network.calladapter.NetworkResponseError import at.connyduck.pixelcat.network.calladapter.NetworkResponseError
import dagger.android.DaggerService import dagger.android.DaggerService
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File import java.io.File
import java.util.* import java.util.Timer
import java.util.TimerTask
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class SendStatusService : DaggerService(), CoroutineScope { class SendStatusService : DaggerService(), CoroutineScope {
@Inject @Inject
@ -41,12 +47,10 @@ class SendStatusService : DaggerService(), CoroutineScope {
private val statusesToSend = ConcurrentHashMap<Int, StatusToSend>() private val statusesToSend = ConcurrentHashMap<Int, StatusToSend>()
private val sendJobs = ConcurrentHashMap<Int, Job>() private val sendJobs = ConcurrentHashMap<Int, Job>()
private val timer = Timer() private val timer = Timer()
private val notificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } private val notificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
return null return null
} }
@ -55,22 +59,21 @@ class SendStatusService : DaggerService(), CoroutineScope {
if (intent.hasExtra(KEY_STATUS)) { if (intent.hasExtra(KEY_STATUS)) {
val tootToSend = intent.getParcelableExtra<StatusToSend>(KEY_STATUS) val tootToSend = intent.getParcelableExtra<StatusToSend>(KEY_STATUS)
?: throw IllegalStateException("SendTootService started without $KEY_STATUS extra") ?: throw IllegalStateException("SendTootService started without $KEY_STATUS extra")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(CHANNEL_ID, getString(R.string.send_status_notification_channel_name), NotificationManager.IMPORTANCE_LOW) val channel = NotificationChannel(CHANNEL_ID, getString(R.string.send_status_notification_channel_name), NotificationManager.IMPORTANCE_LOW)
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
val builder = NotificationCompat.Builder(this, CHANNEL_ID) val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_cat) .setSmallIcon(R.drawable.ic_cat)
.setContentTitle(getString(R.string.send_status_notification_title)) .setContentTitle(getString(R.string.send_status_notification_title))
.setContentText(tootToSend.text) .setContentText(tootToSend.text)
.setProgress(1, 0, true) .setProgress(1, 0, true)
.setOngoing(true) .setOngoing(true)
.setColor(getColorForAttr(android.R.attr.colorPrimary)) .setColor(getColorForAttr(android.R.attr.colorPrimary))
.addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) .addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId))
if (statusesToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (statusesToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH) ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
@ -81,17 +84,14 @@ class SendStatusService : DaggerService(), CoroutineScope {
statusesToSend[sendingNotificationId] = tootToSend statusesToSend[sendingNotificationId] = tootToSend
sendStatus(sendingNotificationId--) sendStatus(sendingNotificationId--)
} else { } else {
if (intent.hasExtra(KEY_CANCEL)) { if (intent.hasExtra(KEY_CANCEL)) {
cancelSending(intent.getIntExtra(KEY_CANCEL, 0)) cancelSending(intent.getIntExtra(KEY_CANCEL, 0))
} }
} }
return START_NOT_STICKY return START_NOT_STICKY
} }
private fun sendStatus(id: Int) { private fun sendStatus(id: Int) {
@ -115,7 +115,6 @@ class SendStatusService : DaggerService(), CoroutineScope {
val mediaIds = statusToSend.mediaUris.map { val mediaIds = statusToSend.mediaUris.map {
var type: String? = null var type: String? = null
val extension = MimeTypeMap.getFileExtensionFromUrl(it) val extension = MimeTypeMap.getFileExtensionFromUrl(it)
if (extension != null) { if (extension != null) {
@ -127,11 +126,14 @@ class SendStatusService : DaggerService(), CoroutineScope {
val body = MultipartBody.Part.create(filePart) val body = MultipartBody.Part.create(filePart)
api.uploadMedia(body).fold({ attachment -> api.uploadMedia(body).fold(
attachment.id { attachment ->
}, { attachment.id
"" },
}) {
""
}
)
} }
val newStatus = NewStatus( val newStatus = NewStatus(
@ -147,42 +149,46 @@ class SendStatusService : DaggerService(), CoroutineScope {
account.domain, account.domain,
statusToSend.idempotencyKey, statusToSend.idempotencyKey,
newStatus newStatus
).fold<Any?>({ ).fold<Any?>(
statusesToSend.remove(id) {
}, { statusesToSend.remove(id)
when(it) { },
is NetworkResponseError.ApiError -> { {
// the server refused to accept the status, save toot & show error message when (it) {
// TODO saveToDrafts is NetworkResponseError.ApiError -> {
// the server refused to accept the status, save toot & show error message
// TODO saveToDrafts
val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID) val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_cat) .setSmallIcon(R.drawable.ic_cat)
.setContentTitle(getString(R.string.send_status_notification_error_title)) .setContentTitle(getString(R.string.send_status_notification_error_title))
//.setContentText(getString(R.string.send_toot_notification_saved_content)) // .setContentText(getString(R.string.send_toot_notification_saved_content))
.setColor(getColorForAttr(android.R.attr.colorPrimary)) .setColor(getColorForAttr(android.R.attr.colorPrimary))
notificationManager.cancel(id) notificationManager.cancel(id)
notificationManager.notify(errorNotificationId--, builder.build()) notificationManager.notify(errorNotificationId--, builder.build())
}
else -> {
var backoff = TimeUnit.SECONDS.toMillis(statusToSend.retries.toLong())
if (backoff > MAX_RETRY_INTERVAL) {
backoff = MAX_RETRY_INTERVAL
} }
else -> {
timer.schedule(object : TimerTask() { var backoff = TimeUnit.SECONDS.toMillis(statusToSend.retries.toLong())
override fun run() { if (backoff > MAX_RETRY_INTERVAL) {
sendStatus(id) backoff = MAX_RETRY_INTERVAL
} }
}, backoff)
timer.schedule(
object : TimerTask() {
override fun run() {
sendStatus(id)
}
},
backoff
)
}
} }
} }
}) )
}.apply { }.apply {
sendJobs[id] = this sendJobs[id] = this
} }
} }
private fun stopSelfWhenDone() { private fun stopSelfWhenDone() {
@ -200,23 +206,25 @@ class SendStatusService : DaggerService(), CoroutineScope {
val sendCall = sendJobs.remove(id) val sendCall = sendJobs.remove(id)
sendCall?.cancel() sendCall?.cancel()
// saveTootToDrafts(tootToCancel) // saveTootToDrafts(tootToCancel)
val builder = NotificationCompat.Builder(this, CHANNEL_ID) val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_cat) .setSmallIcon(R.drawable.ic_cat)
.setContentTitle(getString(R.string.send_status_notification_cancel_title)) .setContentTitle(getString(R.string.send_status_notification_cancel_title))
// .setContentText(getString(R.string.send_toot_notification_saved_content)) // .setContentText(getString(R.string.send_toot_notification_saved_content))
.setColor(getColorForAttr(android.R.attr.colorPrimary)) .setColor(getColorForAttr(android.R.attr.colorPrimary))
notificationManager.notify(id, builder.build()) notificationManager.notify(id, builder.build())
timer.schedule(object : TimerTask() { timer.schedule(
override fun run() { object : TimerTask() {
notificationManager.cancel(id) override fun run() {
stopSelfWhenDone() notificationManager.cancel(id)
} stopSelfWhenDone()
}, 5000) }
},
5000
)
} }
} }
@ -229,7 +237,6 @@ class SendStatusService : DaggerService(), CoroutineScope {
return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
companion object { companion object {
private const val KEY_STATUS = "status" private const val KEY_STATUS = "status"
@ -242,15 +249,15 @@ class SendStatusService : DaggerService(), CoroutineScope {
private var errorNotificationId = Int.MIN_VALUE // use even more negative ids to not clash with other notis private var errorNotificationId = Int.MIN_VALUE // use even more negative ids to not clash with other notis
@JvmStatic @JvmStatic
fun sendStatusIntent(context: Context, fun sendStatusIntent(
statusToSend: StatusToSend context: Context,
statusToSend: StatusToSend
): Intent { ): Intent {
val intent = Intent(context, SendStatusService::class.java) val intent = Intent(context, SendStatusService::class.java)
intent.putExtra(KEY_STATUS, statusToSend) intent.putExtra(KEY_STATUS, statusToSend)
return intent return intent
} }
} }
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
@ -262,11 +269,11 @@ data class StatusToSend(
val accountId: Long, val accountId: Long,
val idempotencyKey: String = UUID.randomUUID().toString(), val idempotencyKey: String = UUID.randomUUID().toString(),
val text: String, val text: String,
val visibility: String, val visibility: String,
val sensitive: Boolean, val sensitive: Boolean,
val mediaUris: List<String>, val mediaUris: List<String>,
val mediaDescriptions: List<String> = emptyList(), val mediaDescriptions: List<String> = emptyList(),
val inReplyToId: String? = null, val inReplyToId: String? = null,
val savedTootUid: Int = 0, val savedTootUid: Int = 0,
var retries: Int = 0 var retries: Int = 0
) : Parcelable ) : Parcelable

View File

@ -6,7 +6,7 @@ import at.connyduck.pixelcat.components.settings.AppSettings
import dagger.android.support.DaggerAppCompatActivity import dagger.android.support.DaggerAppCompatActivity
import javax.inject.Inject import javax.inject.Inject
abstract class BaseActivity: DaggerAppCompatActivity() { abstract class BaseActivity : DaggerAppCompatActivity() {
@Inject @Inject
lateinit var appSettings: AppSettings lateinit var appSettings: AppSettings
@ -15,10 +15,8 @@ abstract class BaseActivity: DaggerAppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
theme.applyStyle(appSettings.getAppColorStyle(), true) theme.applyStyle(appSettings.getAppColorStyle(), true)
if(!appSettings.useSystemFont()) { if (!appSettings.useSystemFont()) {
theme.applyStyle(R.style.NunitoFont, true) theme.applyStyle(R.style.NunitoFont, true)
} }
} }
}
}

View File

@ -18,7 +18,6 @@ import at.connyduck.pixelcat.databinding.ActivityLoginBinding
import at.connyduck.pixelcat.util.viewBinding import at.connyduck.pixelcat.util.viewBinding
import javax.inject.Inject import javax.inject.Inject
class LoginActivity : BaseActivity(), Observer<LoginModel> { class LoginActivity : BaseActivity(), Observer<LoginModel> {
@Inject @Inject
@ -58,7 +57,7 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val authCode = data?.getStringExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE) val authCode = data?.getStringExtra(LoginWebViewActivity.RESULT_AUTHORIZATION_CODE)
if(requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK && !authCode.isNullOrEmpty()) { if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK && !authCode.isNullOrEmpty()) {
loginViewModel.authCode(authCode) loginViewModel.authCode(authCode)
return return
} }
@ -72,7 +71,7 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) { when (item.itemId) {
R.id.navigation_settings -> { R.id.navigation_settings -> {
startActivity(SettingsActivity.newIntent(this)) startActivity(SettingsActivity.newIntent(this))
return true return true
@ -89,17 +88,16 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
override fun onChanged(loginModel: LoginModel?) { override fun onChanged(loginModel: LoginModel?) {
binding.loginInput.setText(loginModel?.input) binding.loginInput.setText(loginModel?.input)
if(loginModel == null) { if (loginModel == null) {
return return
} }
when(loginModel.state) { when (loginModel.state) {
LoginState.NO_ERROR -> binding.loginInputLayout.error = null LoginState.NO_ERROR -> binding.loginInputLayout.error = null
LoginState.AUTH_ERROR -> binding.loginInputLayout.error = "auth error" LoginState.AUTH_ERROR -> binding.loginInputLayout.error = "auth error"
LoginState.INVALID_DOMAIN -> binding.loginInputLayout.error = "invalid domain" LoginState.INVALID_DOMAIN -> binding.loginInputLayout.error = "invalid domain"
LoginState.NETWORK_ERROR -> binding.loginInputLayout.error = "network error" LoginState.NETWORK_ERROR -> binding.loginInputLayout.error = "network error"
LoginState.LOADING -> { LoginState.LOADING -> {
} }
LoginState.SUCCESS -> { LoginState.SUCCESS -> {
startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE) startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE)
@ -114,5 +112,4 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
companion object { companion object {
private const val REQUEST_CODE = 14 private const val REQUEST_CODE = 14
} }
} }

View File

@ -10,10 +10,8 @@ data class LoginModel(
val domain: String? = null, val domain: String? = null,
val clientId: String? = null, val clientId: String? = null,
val clientSecret: String? = null val clientSecret: String? = null
): Parcelable ) : Parcelable
enum class LoginState { //TODO rename this stuff so it makes sense enum class LoginState { // TODO rename this stuff so it makes sense
LOADING, NO_ERROR, NETWORK_ERROR, INVALID_DOMAIN, AUTH_ERROR, SUCCESS, SUCCESS_FINAL LOADING, NO_ERROR, NETWORK_ERROR, INVALID_DOMAIN, AUTH_ERROR, SUCCESS, SUCCESS_FINAL
} }

View File

@ -10,14 +10,13 @@ import at.connyduck.pixelcat.db.entitity.AccountAuthData
import at.connyduck.pixelcat.network.FediverseApi import at.connyduck.pixelcat.network.FediverseApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.HttpUrl import okhttp3.HttpUrl
import java.util.* import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
class LoginViewModel @Inject constructor( class LoginViewModel @Inject constructor(
private val fediverseApi: FediverseApi, private val fediverseApi: FediverseApi,
private val accountManager: AccountManager private val accountManager: AccountManager
): ViewModel() { ) : ViewModel() {
val loginState = MutableLiveData<LoginModel>().apply { val loginState = MutableLiveData<LoginModel>().apply {
value = LoginModel(state = LoginState.NO_ERROR) value = LoginModel(state = LoginState.NO_ERROR)
@ -35,18 +34,17 @@ class LoginViewModel @Inject constructor(
return return
} }
val exceptionMatch = Config.domainExceptions.any {exception -> val exceptionMatch = Config.domainExceptions.any { exception ->
domainInput.equals(exception, true) || domainInput.endsWith(".$exception", true) domainInput.equals(exception, true) || domainInput.endsWith(".$exception", true)
} }
if(exceptionMatch) { if (exceptionMatch) {
loginState.value = LoginModel(input, LoginState.AUTH_ERROR) loginState.value = LoginModel(input, LoginState.AUTH_ERROR)
return return
} }
loginState.value = LoginModel(input, LoginState.LOADING) loginState.value = LoginModel(input, LoginState.LOADING)
viewModelScope.launch { viewModelScope.launch {
fediverseApi.authenticateAppAsync( fediverseApi.authenticateAppAsync(
domain = domainInput, domain = domainInput,
@ -54,14 +52,15 @@ class LoginViewModel @Inject constructor(
clientWebsite = Config.website, clientWebsite = Config.website,
redirectUris = Config.oAuthRedirect, redirectUris = Config.oAuthRedirect,
scopes = Config.oAuthScopes scopes = Config.oAuthScopes
).fold({ appData -> ).fold(
loginState.postValue(LoginModel(input, LoginState.SUCCESS, domainInput, appData.clientId, appData.clientSecret)) { appData ->
}, { loginState.postValue(LoginModel(input, LoginState.SUCCESS, domainInput, appData.clientId, appData.clientSecret))
loginState.postValue(LoginModel(input, LoginState.AUTH_ERROR)) },
}) {
loginState.postValue(LoginModel(input, LoginState.AUTH_ERROR))
}
)
} }
} }
@MainThread @MainThread
@ -75,25 +74,27 @@ class LoginViewModel @Inject constructor(
clientSecret = loginModel.clientSecret!!, clientSecret = loginModel.clientSecret!!,
redirectUri = Config.oAuthRedirect, redirectUri = Config.oAuthRedirect,
code = authCode code = authCode
).fold({ tokenResponse -> ).fold(
val authData = AccountAuthData( { tokenResponse ->
accessToken = tokenResponse.accessToken, val authData = AccountAuthData(
refreshToken = tokenResponse.refreshToken, accessToken = tokenResponse.accessToken,
tokenExpiresAt = tokenResponse.createdAt ?: 0 + (tokenResponse.expiresIn refreshToken = tokenResponse.refreshToken,
?: 0), tokenExpiresAt = tokenResponse.createdAt ?: 0 + (
clientId = loginModel.clientId, tokenResponse.expiresIn
clientSecret = loginModel.clientSecret ?: 0
) ),
accountManager.addAccount(loginModel.domain, authData) clientId = loginModel.clientId,
loginState.postValue(loginState.value?.copy(state = LoginState.SUCCESS_FINAL)) clientSecret = loginModel.clientSecret
}, { )
accountManager.addAccount(loginModel.domain, authData)
}) loginState.postValue(loginState.value?.copy(state = LoginState.SUCCESS_FINAL))
},
{
}
)
} }
} }
private fun canonicalizeDomain(domain: String): String { private fun canonicalizeDomain(domain: String): String {
// Strip any schemes out. // Strip any schemes out.
var s = domain.replaceFirst("http://", "") var s = domain.replaceFirst("http://", "")
@ -105,4 +106,4 @@ class LoginViewModel @Inject constructor(
} }
return s.trim().toLowerCase(Locale.ROOT) return s.trim().toLowerCase(Locale.ROOT)
} }
} }

View File

@ -12,7 +12,6 @@ import at.connyduck.pixelcat.config.Config
import android.webkit.WebViewClient import android.webkit.WebViewClient
import at.connyduck.pixelcat.databinding.ActivityLoginWebViewBinding import at.connyduck.pixelcat.databinding.ActivityLoginWebViewBinding
class LoginWebViewActivity : AppCompatActivity() { class LoginWebViewActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginWebViewBinding private lateinit var binding: ActivityLoginWebViewBinding
@ -36,10 +35,10 @@ class LoginWebViewActivity : AppCompatActivity() {
val url = "https://" + domain + endpoint + "?" + toQueryString(parameters) val url = "https://" + domain + endpoint + "?" + toQueryString(parameters)
binding.loginWebView.webViewClient = object: WebViewClient() { binding.loginWebView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
if(request.url.scheme == Config.oAuthScheme && request.url.host == Config.oAuthHost) { if (request.url.scheme == Config.oAuthScheme && request.url.host == Config.oAuthHost) {
loginSuccess(request.url.getQueryParameter("code").orEmpty()) loginSuccess(request.url.getQueryParameter("code").orEmpty())
return true return true
} }
@ -48,7 +47,6 @@ class LoginWebViewActivity : AppCompatActivity() {
} }
} }
binding.loginWebView.loadUrl(url) binding.loginWebView.loadUrl(url)
} }
private fun loginSuccess(authCode: String) { private fun loginSuccess(authCode: String) {
@ -59,7 +57,6 @@ class LoginWebViewActivity : AppCompatActivity() {
finish() finish()
} }
private fun toQueryString(parameters: Map<String, String>): String { private fun toQueryString(parameters: Map<String, String>): String {
return parameters.map { "${it.key}=${Uri.encode(it.value)}" } return parameters.map { "${it.key}=${Uri.encode(it.value)}" }
.joinToString("&") .joinToString("&")

View File

@ -61,7 +61,6 @@ class MainActivity : BaseActivity() {
true true
} }
else -> false else -> false
} }
} }
@ -87,7 +86,6 @@ class MainActivity : BaseActivity() {
binding.navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener) binding.navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
mainViewModel.whatever() mainViewModel.whatever()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -100,7 +98,4 @@ class MainActivity : BaseActivity() {
startActivity(ComposeActivity.newIntent(this, returnValue?.firstOrNull()!!)) startActivity(ComposeActivity.newIntent(this, returnValue?.firstOrNull()!!))
} }
} }
} }

View File

@ -8,10 +8,10 @@ import at.connyduck.pixelcat.components.profile.ProfileFragment
import at.connyduck.pixelcat.components.search.SearchFragment import at.connyduck.pixelcat.components.search.SearchFragment
import at.connyduck.pixelcat.components.timeline.TimelineFragment import at.connyduck.pixelcat.components.timeline.TimelineFragment
class MainFragmentAdapter(fragmentActivity: FragmentActivity): FragmentStateAdapter(fragmentActivity) { class MainFragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return when(position) { return when (position) {
0 -> TimelineFragment.newInstance() 0 -> TimelineFragment.newInstance()
1 -> SearchFragment.newInstance() 1 -> SearchFragment.newInstance()
2 -> NotificationsFragment.newInstance() 2 -> NotificationsFragment.newInstance()
@ -23,5 +23,4 @@ class MainFragmentAdapter(fragmentActivity: FragmentActivity): FragmentStateAdap
} }
override fun getItemCount() = 4 override fun getItemCount() = 4
}
}

View File

@ -10,21 +10,21 @@ import javax.inject.Inject
class MainViewModel @Inject constructor( class MainViewModel @Inject constructor(
private val fediverseApi: FediverseApi, private val fediverseApi: FediverseApi,
private val accountManager: AccountManager private val accountManager: AccountManager
): ViewModel() { ) : ViewModel() {
fun whatever() { fun whatever() {
} }
init { init {
viewModelScope.launch { viewModelScope.launch {
fediverseApi.accountVerifyCredentials().fold({ account -> fediverseApi.accountVerifyCredentials().fold(
{ account ->
accountManager.updateActiveAccount(account) accountManager.updateActiveAccount(account)
}, { },
{
}) }
)
} }
} }
}
}

View File

@ -6,7 +6,7 @@ import at.connyduck.pixelcat.dagger.ViewModelFactory
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import javax.inject.Inject import javax.inject.Inject
class NotificationsFragment: DaggerFragment(R.layout.fragment_notifications) { class NotificationsFragment : DaggerFragment(R.layout.fragment_notifications) {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory
@ -16,5 +16,4 @@ class NotificationsFragment: DaggerFragment(R.layout.fragment_notifications) {
companion object { companion object {
fun newInstance() = NotificationsFragment() fun newInstance() = NotificationsFragment()
} }
}
}

View File

@ -3,10 +3,4 @@ package at.connyduck.pixelcat.components.notifications
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import javax.inject.Inject import javax.inject.Inject
class NotificationsViewModel @Inject constructor( class NotificationsViewModel @Inject constructor() : ViewModel()
): ViewModel() {
}

View File

@ -18,7 +18,7 @@ class GridSpacingItemDecoration(
state: RecyclerView.State state: RecyclerView.State
) { ) {
val position = parent.getChildAdapterPosition(view) // item position val position = parent.getChildAdapterPosition(view) // item position
if(position < topOffset) return if (position < topOffset) return
val column = (position - topOffset) % spanCount // item column val column = (position - topOffset) % spanCount // item column
@ -28,7 +28,5 @@ class GridSpacingItemDecoration(
if (position - topOffset >= spanCount) { if (position - topOffset >= spanCount) {
outRect.top = spacing // item top outRect.top = spacing // item top
} }
} }
}
}

View File

@ -8,7 +8,7 @@ import at.connyduck.pixelcat.R
import at.connyduck.pixelcat.components.general.BaseActivity import at.connyduck.pixelcat.components.general.BaseActivity
import at.connyduck.pixelcat.databinding.ActivityProfileBinding import at.connyduck.pixelcat.databinding.ActivityProfileBinding
class ProfileActivity: BaseActivity() { class ProfileActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -30,7 +30,6 @@ class ProfileActivity: BaseActivity() {
add(R.id.layoutContainer, ProfileFragment.newInstance(intent.getStringExtra(EXTRA_ACCOUNT_ID))) add(R.id.layoutContainer, ProfileFragment.newInstance(intent.getStringExtra(EXTRA_ACCOUNT_ID)))
} }
} }
} }
companion object { companion object {
@ -42,4 +41,4 @@ class ProfileActivity: BaseActivity() {
} }
} }
} }
} }

View File

@ -22,13 +22,12 @@ class ProfileDataSourceFactory(
} }
} }
class ProfileImageDataSource( class ProfileImageDataSource(
private val api: FediverseApi, private val api: FediverseApi,
private val accountId: String?, private val accountId: String?,
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val scope: CoroutineScope private val scope: CoroutineScope
): ItemKeyedDataSource<String, Status>() { ) : ItemKeyedDataSource<String, Status>() {
override fun loadInitial( override fun loadInitial(
params: LoadInitialParams<String>, params: LoadInitialParams<String>,
callback: LoadInitialCallback<Status> callback: LoadInitialCallback<Status>
@ -40,11 +39,13 @@ class ProfileImageDataSource(
limit = params.requestedLoadSize, limit = params.requestedLoadSize,
onlyMedia = true, onlyMedia = true,
excludeReblogs = true excludeReblogs = true
).fold({ ).fold(
callback.onResult(it) {
}, { callback.onResult(it)
},
}) {
}
)
} }
} }
@ -57,11 +58,13 @@ class ProfileImageDataSource(
limit = params.requestedLoadSize, limit = params.requestedLoadSize,
onlyMedia = true, onlyMedia = true,
excludeReblogs = true excludeReblogs = true
).fold({ ).fold(
callback.onResult(it) {
}, { callback.onResult(it)
},
}) {
}
)
} }
} }
@ -70,4 +73,4 @@ class ProfileImageDataSource(
} }
override fun getKey(item: Status) = item.id override fun getKey(item: Status) = item.id
} }

View File

@ -1,7 +1,7 @@
package at.connyduck.pixelcat.components.profile package at.connyduck.pixelcat.components.profile
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.View
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -43,7 +43,7 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if(activity is MainActivity) { if (activity is MainActivity) {
binding.toolbar.inflateMenu(R.menu.secondary_navigation) binding.toolbar.inflateMenu(R.menu.secondary_navigation)
binding.toolbar.setOnMenuItemClickListener { binding.toolbar.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
@ -58,7 +58,6 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
MenuBottomSheet() MenuBottomSheet()
bottomSheetDialog.show(childFragmentManager, "menuBottomSheet") bottomSheetDialog.show(childFragmentManager, "menuBottomSheet")
} }
} }
true true
} }
@ -74,9 +73,9 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
val imageSize = (displayWidth - (IMAGE_COLUMN_COUNT - 1) * imageSpacing) / IMAGE_COLUMN_COUNT val imageSize = (displayWidth - (IMAGE_COLUMN_COUNT - 1) * imageSpacing) / IMAGE_COLUMN_COUNT
imageAdapter = ProfileImageAdapter(imageSize) imageAdapter = ProfileImageAdapter(imageSize)
val layoutManager = GridLayoutManager(view.context, IMAGE_COLUMN_COUNT) val layoutManager = GridLayoutManager(view.context, IMAGE_COLUMN_COUNT)
layoutManager.spanSizeLookup = object: GridLayoutManager.SpanSizeLookup() { layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
if(position == 0) return IMAGE_COLUMN_COUNT if (position == 0) return IMAGE_COLUMN_COUNT
return 1 return 1
} }
} }
@ -88,21 +87,30 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
viewModel.setAccountInfo(arg(ACCOUNT_ID)) viewModel.setAccountInfo(arg(ACCOUNT_ID))
viewModel.profile.observe(viewLifecycleOwner, Observer { viewModel.profile.observe(
when (it) { viewLifecycleOwner,
is Success -> onAccountChanged(it.data) Observer {
is Error -> showError() when (it) {
is Success -> onAccountChanged(it.data)
is Error -> showError()
}
} }
}) )
viewModel.relationship.observe(viewLifecycleOwner, Observer { viewModel.relationship.observe(
when (it) { viewLifecycleOwner,
is Success -> onRelationshipChanged(it.data) Observer {
is Error -> showError() when (it) {
is Success -> onRelationshipChanged(it.data)
is Error -> showError()
}
} }
}) )
viewModel.profileImages.observe(viewLifecycleOwner, Observer { viewModel.profileImages.observe(
imageAdapter.submitList(it) viewLifecycleOwner,
}) Observer {
imageAdapter.submitList(it)
}
)
} }
private fun onAccountChanged(account: Account?) { private fun onAccountChanged(account: Account?) {
@ -112,7 +120,6 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
binding.toolbar.title = account.displayName binding.toolbar.title = account.displayName
headerAdapter.setAccount(account, viewModel.isSelf) headerAdapter.setAccount(account, viewModel.isSelf)
} }
private fun onRelationshipChanged(relation: Relationship?) { private fun onRelationshipChanged(relation: Relationship?) {
@ -138,5 +145,4 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
fun newInstance(accountId: String? = null) = fun newInstance(accountId: String? = null) =
ProfileFragment().withArgs { putString(ACCOUNT_ID, accountId) } ProfileFragment().withArgs { putString(ACCOUNT_ID, accountId) }
} }
}
}

View File

@ -12,7 +12,7 @@ import coil.api.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import java.text.NumberFormat import java.text.NumberFormat
class ProfileHeaderAdapter: RecyclerView.Adapter<ProfileHeaderViewHolder>() { class ProfileHeaderAdapter : RecyclerView.Adapter<ProfileHeaderViewHolder>() {
private var account: Account? = null private var account: Account? = null
private var isSelf: Boolean = false private var isSelf: Boolean = false
@ -61,7 +61,7 @@ class ProfileHeaderAdapter: RecyclerView.Adapter<ProfileHeaderViewHolder>() {
} }
if (payloads.isEmpty() || payloads.contains(RELATIONSHIP_CHANGED)) { if (payloads.isEmpty() || payloads.contains(RELATIONSHIP_CHANGED)) {
relationship?.let { relationship?.let {
if(it.following) { if (it.following) {
holder.binding.profileFollowButton.setText(R.string.profile_follows_you) holder.binding.profileFollowButton.setText(R.string.profile_follows_you)
} else { } else {
holder.binding.profileFollowButton.setText(R.string.profile_action_follow) holder.binding.profileFollowButton.setText(R.string.profile_action_follow)
@ -79,5 +79,4 @@ class ProfileHeaderAdapter: RecyclerView.Adapter<ProfileHeaderViewHolder>() {
} }
} }
class ProfileHeaderViewHolder(val binding: ItemProfileHeaderBinding) : RecyclerView.ViewHolder(binding.root)
class ProfileHeaderViewHolder(val binding: ItemProfileHeaderBinding): RecyclerView.ViewHolder(binding.root)

View File

@ -15,8 +15,8 @@ import coil.api.load
class ProfileImageAdapter( class ProfileImageAdapter(
private val imageSizePx: Int private val imageSizePx: Int
): PagedListAdapter<Status, ProfileImageViewHolder>( ) : PagedListAdapter<Status, ProfileImageViewHolder>(
object: DiffUtil.ItemCallback<Status>() { object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(old: Status, new: Status): Boolean { override fun areItemsTheSame(old: Status, new: Status): Boolean {
return false return false
} }
@ -54,8 +54,6 @@ class ProfileImageAdapter(
} }
} }
} }
} }
class ProfileImageViewHolder(val binding: ItemProfileImageBinding) : RecyclerView.ViewHolder(binding.root)
class ProfileImageViewHolder(val binding: ItemProfileImageBinding): RecyclerView.ViewHolder(binding.root)

View File

@ -19,11 +19,11 @@ import javax.inject.Inject
class ProfileViewModel @Inject constructor( class ProfileViewModel @Inject constructor(
private val fediverseApi: FediverseApi, private val fediverseApi: FediverseApi,
private val accountManager: AccountManager private val accountManager: AccountManager
): ViewModel() { ) : ViewModel() {
val profile = MutableLiveData<UiState<Account>>() val profile = MutableLiveData<UiState<Account>>()
val relationship = MutableLiveData<UiState<Relationship>>() val relationship = MutableLiveData<UiState<Relationship>>()
val profileImages = MutableLiveData<PagedList<Status>>() val profileImages = MutableLiveData<PagedList<Status>>()
val isSelf: Boolean val isSelf: Boolean
get() = accountId == null get() = accountId == null
@ -46,11 +46,14 @@ class ProfileViewModel @Inject constructor(
private fun loadAccount(reload: Boolean = false) { private fun loadAccount(reload: Boolean = false) {
if (profile.value == null || reload) { if (profile.value == null || reload) {
viewModelScope.launch { viewModelScope.launch {
fediverseApi.account(getAccountId()).fold({ fediverseApi.account(getAccountId()).fold(
profile.value = Success(it) {
}, { profile.value = Success(it)
profile.value = Error(cause = it) },
}) {
profile.value = Error(cause = it)
}
)
} }
} }
} }
@ -58,24 +61,28 @@ class ProfileViewModel @Inject constructor(
private fun loadRelationship(reload: Boolean = false) { private fun loadRelationship(reload: Boolean = false) {
if (relationship.value == null || reload) { if (relationship.value == null || reload) {
viewModelScope.launch { viewModelScope.launch {
fediverseApi.relationships(listOf(getAccountId())).fold({ fediverseApi.relationships(listOf(getAccountId())).fold(
relationship.value = Success(it.first()) {
}, { relationship.value = Success(it.first())
relationship.value = Error(cause = it) },
}) {
relationship.value = Error(cause = it)
}
)
} }
} }
} }
private fun loadImages(reload: Boolean = false) { private fun loadImages(reload: Boolean = false) {
if(profileImages.value == null || reload) { if (profileImages.value == null || reload) {
profileImages.value = PagedList.Builder( profileImages.value = PagedList.Builder(
ProfileImageDataSource( ProfileImageDataSource(
fediverseApi, fediverseApi,
accountId, accountId,
accountManager, accountManager,
viewModelScope viewModelScope
), 20 ),
20
).setNotifyExecutor(Executors.mainThreadExecutor()) ).setNotifyExecutor(Executors.mainThreadExecutor())
.setFetchExecutor(java.util.concurrent.Executors.newSingleThreadExecutor()) .setFetchExecutor(java.util.concurrent.Executors.newSingleThreadExecutor())
.build() .build()
@ -85,5 +92,4 @@ class ProfileViewModel @Inject constructor(
private suspend fun getAccountId(): String { private suspend fun getAccountId(): String {
return accountId ?: accountManager.activeAccount()?.accountId!! return accountId ?: accountManager.activeAccount()?.accountId!!
} }
}
}

View File

@ -6,7 +6,7 @@ import at.connyduck.pixelcat.dagger.ViewModelFactory
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import javax.inject.Inject import javax.inject.Inject
class SearchFragment: DaggerFragment(R.layout.fragment_search) { class SearchFragment : DaggerFragment(R.layout.fragment_search) {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory
@ -16,5 +16,4 @@ class SearchFragment: DaggerFragment(R.layout.fragment_search) {
companion object { companion object {
fun newInstance() = SearchFragment() fun newInstance() = SearchFragment()
} }
}
}

View File

@ -3,10 +3,4 @@ package at.connyduck.pixelcat.components.search
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import javax.inject.Inject import javax.inject.Inject
class SearchViewModel @Inject constructor( class SearchViewModel @Inject constructor() : ViewModel()
): ViewModel() {
}

View File

@ -4,21 +4,23 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.* import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_TIME
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import at.connyduck.pixelcat.R import at.connyduck.pixelcat.R
import javax.inject.Inject import javax.inject.Inject
class AppSettings @Inject constructor ( class AppSettings @Inject constructor (
private val sharedPrefs: SharedPreferences, private val sharedPrefs: SharedPreferences,
private val context: Context private val context: Context
) { ) {
@StyleRes @StyleRes
fun getAppColorStyle(): Int { fun getAppColorStyle(): Int {
val appColorPref = sharedPrefs.getString( val appColorPref = sharedPrefs.getString(
context.getString(R.string.key_pref_app_color), context.getString(R.string.key_pref_app_color),
context.getString(R.string.key_pref_app_color_default) context.getString(R.string.key_pref_app_color_default)
) )
return when (appColorPref) { return when (appColorPref) {
@ -26,14 +28,13 @@ class AppSettings @Inject constructor (
context.getString(R.string.key_pref_app_color_cold) -> R.style.Cold context.getString(R.string.key_pref_app_color_cold) -> R.style.Cold
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }
@AppCompatDelegate.NightMode @AppCompatDelegate.NightMode
fun getNightMode(): Int { fun getNightMode(): Int {
val nightModePref = sharedPrefs.getString( val nightModePref = sharedPrefs.getString(
context.getString(R.string.key_pref_night_mode), context.getString(R.string.key_pref_night_mode),
context.getString(R.string.key_pref_night_mode_default) context.getString(R.string.key_pref_night_mode_default)
) )
return when (nightModePref) { return when (nightModePref) {
@ -47,22 +48,19 @@ class AppSettings @Inject constructor (
fun isBlackNightMode(): Boolean { fun isBlackNightMode(): Boolean {
return sharedPrefs.getBoolean( return sharedPrefs.getBoolean(
context.getString(R.string.key_pref_black_night_mode), context.getString(R.string.key_pref_black_night_mode),
context.resources.getBoolean(R.bool.pref_title_black_night_mode_default) context.resources.getBoolean(R.bool.pref_title_black_night_mode_default)
) )
} }
fun useSystemFont(): Boolean { fun useSystemFont(): Boolean {
return sharedPrefs.getBoolean( return sharedPrefs.getBoolean(
context.getString(R.string.key_pref_system_font), context.getString(R.string.key_pref_system_font),
context.resources.getBoolean(R.bool.pref_title_system_font_default) context.resources.getBoolean(R.bool.pref_title_system_font_default)
) )
} }
} }
private fun SharedPreferences.getNonNullString(key: String, default: String): String { private fun SharedPreferences.getNonNullString(key: String, default: String): String {
return getString(key, default) ?: default return getString(key, default) ?: default
} }

View File

@ -48,11 +48,10 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
preferences.registerOnSharedPreferenceChangeListener(this) preferences.registerOnSharedPreferenceChangeListener(this)
restartActivitiesOnExit = intent.getBooleanExtra(EXTRA_RESTART_ACTIVITIES, false) restartActivitiesOnExit = intent.getBooleanExtra(EXTRA_RESTART_ACTIVITIES, false)
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when(key) { when (key) {
getString(R.string.key_pref_app_color) -> restartCurrentActivity() getString(R.string.key_pref_app_color) -> restartCurrentActivity()
getString(R.string.key_pref_night_mode) -> AppCompatDelegate.setDefaultNightMode(appSettings.getNightMode()) getString(R.string.key_pref_night_mode) -> AppCompatDelegate.setDefaultNightMode(appSettings.getNightMode())
} }
@ -68,7 +67,7 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
override fun onBackPressed() { override fun onBackPressed() {
val parentActivityName = intent.getStringExtra(EXTRA_PARENT_ACTIVITY) val parentActivityName = intent.getStringExtra(EXTRA_PARENT_ACTIVITY)
if(restartActivitiesOnExit && parentActivityName != null) { if (restartActivitiesOnExit && parentActivityName != null) {
val restartIntent = Intent() val restartIntent = Intent()
restartIntent.component = ComponentName(this, intent.getStringExtra(EXTRA_PARENT_ACTIVITY)!!) restartIntent.component = ComponentName(this, intent.getStringExtra(EXTRA_PARENT_ACTIVITY)!!)
restartIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK restartIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
@ -100,6 +99,4 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
} }
} }
} }
}
}

View File

@ -10,7 +10,7 @@ import dagger.android.support.DaggerAppCompatActivity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class SplashActivity: DaggerAppCompatActivity() { class SplashActivity : DaggerAppCompatActivity() {
@Inject @Inject
lateinit var accountManager: AccountManager lateinit var accountManager: AccountManager
@ -24,7 +24,7 @@ class SplashActivity: DaggerAppCompatActivity() {
Intent( Intent(
this@SplashActivity, this@SplashActivity,
MainActivity::class.java MainActivity::class.java
) //TODO don't create intents here ) // TODO don't create intents here
} else { } else {
Intent(this@SplashActivity, LoginActivity::class.java) Intent(this@SplashActivity, LoginActivity::class.java)
} }
@ -32,4 +32,4 @@ class SplashActivity: DaggerAppCompatActivity() {
finish() finish()
} }
} }
} }

View File

@ -1,7 +1,6 @@
package at.connyduck.pixelcat.components.timeline package at.connyduck.pixelcat.components.timeline
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View import android.view.View
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -14,13 +13,12 @@ import at.connyduck.pixelcat.dagger.ViewModelFactory
import at.connyduck.pixelcat.databinding.FragmentTimelineBinding import at.connyduck.pixelcat.databinding.FragmentTimelineBinding
import at.connyduck.pixelcat.db.entitity.StatusEntity import at.connyduck.pixelcat.db.entitity.StatusEntity
import at.connyduck.pixelcat.util.viewBinding import at.connyduck.pixelcat.util.viewBinding
import com.google.android.material.appbar.AppBarLayout
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class TimelineFragment: DaggerFragment(R.layout.fragment_timeline), TimeLineActionListener { class TimelineFragment : DaggerFragment(R.layout.fragment_timeline), TimeLineActionListener {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory
@ -61,9 +59,7 @@ class TimelineFragment: DaggerFragment(R.layout.fragment_timeline), TimeLineActi
binding.timelineSwipeRefresh.isRefreshing = false binding.timelineSwipeRefresh.isRefreshing = false
} }
// viewModel.posts.observe(viewLifecycleOwner, Observer { t -> adapter.submitList(t) })
//viewModel.posts.observe(viewLifecycleOwner, Observer { t -> adapter.submitList(t) })
} }
companion object { companion object {
@ -81,5 +77,4 @@ class TimelineFragment: DaggerFragment(R.layout.fragment_timeline), TimeLineActi
override fun onReply(status: StatusEntity) { override fun onReply(status: StatusEntity) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
}
}

View File

@ -7,7 +7,7 @@ import at.connyduck.pixelcat.databinding.ItemTimelineImageBinding
import at.connyduck.pixelcat.model.Attachment import at.connyduck.pixelcat.model.Attachment
import coil.api.load import coil.api.load
class TimelineImageAdapter: RecyclerView.Adapter<TimelineImageViewHolder>() { class TimelineImageAdapter : RecyclerView.Adapter<TimelineImageViewHolder>() {
var images: List<Attachment> = emptyList() var images: List<Attachment> = emptyList()
set(value) { set(value) {
@ -25,10 +25,7 @@ class TimelineImageAdapter: RecyclerView.Adapter<TimelineImageViewHolder>() {
override fun onBindViewHolder(holder: TimelineImageViewHolder, position: Int) { override fun onBindViewHolder(holder: TimelineImageViewHolder, position: Int) {
holder.binding.timelineImageView.load(images[position].previewUrl) holder.binding.timelineImageView.load(images[position].previewUrl)
} }
} }
class TimelineImageViewHolder(val binding: ItemTimelineImageBinding) : RecyclerView.ViewHolder(binding.root)
class TimelineImageViewHolder(val binding: ItemTimelineImageBinding): RecyclerView.ViewHolder(binding.root)

View File

@ -20,7 +20,7 @@ interface TimeLineActionListener {
fun onReply(status: StatusEntity) fun onReply(status: StatusEntity)
} }
object TimelineDiffUtil: DiffUtil.ItemCallback<StatusEntity>() { object TimelineDiffUtil : DiffUtil.ItemCallback<StatusEntity>() {
override fun areItemsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean { override fun areItemsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean {
return oldItem.id == newItem.id return oldItem.id == newItem.id
} }
@ -28,7 +28,6 @@ object TimelineDiffUtil: DiffUtil.ItemCallback<StatusEntity>() {
override fun areContentsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean { override fun areContentsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean {
return oldItem == newItem return oldItem == newItem
} }
} }
class TimelineListAdapter( class TimelineListAdapter(
@ -83,22 +82,17 @@ class TimelineListAdapter(
holder.binding.postDescription.text = status.content.parseAsHtml().trim() holder.binding.postDescription.text = status.content.parseAsHtml().trim()
holder.binding.postDate.text = dateTimeFormatter.format(status.createdAt) holder.binding.postDate.text = dateTimeFormatter.format(status.createdAt)
} }
} }
} }
class TimelineViewHolder(val binding: ItemStatusBinding) : RecyclerView.ViewHolder(binding.root) {
class TimelineViewHolder(val binding: ItemStatusBinding): RecyclerView.ViewHolder(binding.root) {
init { init {
binding.postImages.adapter = TimelineImageAdapter() binding.postImages.adapter = TimelineImageAdapter()
binding.postIndicator.setViewPager(binding.postImages) binding.postIndicator.setViewPager(binding.postImages)
(binding.postImages.adapter as TimelineImageAdapter).registerAdapterDataObserver(binding.postIndicator.adapterDataObserver) (binding.postImages.adapter as TimelineImageAdapter).registerAdapterDataObserver(binding.postIndicator.adapterDataObserver)
// val snapHelper = PagerSnapHelper() // val snapHelper = PagerSnapHelper()
// snapHelper.attachToRecyclerView(binding.postImages) // snapHelper.attachToRecyclerView(binding.postImages)
} }
} }

View File

@ -16,7 +16,7 @@ class TimelineRemoteMediator(
private val accountId: Long, private val accountId: Long,
private val api: FediverseApi, private val api: FediverseApi,
private val db: AppDatabase private val db: AppDatabase
): RemoteMediator<Int, StatusEntity>() { ) : RemoteMediator<Int, StatusEntity>() {
override suspend fun load( override suspend fun load(
loadType: LoadType, loadType: LoadType,
@ -36,20 +36,21 @@ class TimelineRemoteMediator(
} }
} }
return apiCall.fold({ statusResult -> return apiCall.fold(
{ statusResult ->
db.withTransaction { db.withTransaction {
if (loadType == LoadType.REFRESH) { if (loadType == LoadType.REFRESH) {
db.statusDao().clearAll(accountId) db.statusDao().clearAll(accountId)
} }
db.statusDao().insertOrReplace(statusResult.map { it.toEntity(accountId) }) db.statusDao().insertOrReplace(statusResult.map { it.toEntity(accountId) })
} }
MediatorResult.Success(endOfPaginationReached = statusResult.isEmpty()) MediatorResult.Success(endOfPaginationReached = statusResult.isEmpty())
}, { },
MediatorResult.Error(it) {
}) MediatorResult.Error(it)
}
)
} }
override suspend fun initialize() = InitializeAction.SKIP_INITIAL_REFRESH override suspend fun initialize() = InitializeAction.SKIP_INITIAL_REFRESH
} }

View File

@ -1,22 +1,25 @@
package at.connyduck.pixelcat.components.timeline package at.connyduck.pixelcat.components.timeline
import androidx.lifecycle.* import androidx.lifecycle.MutableLiveData
import androidx.paging.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import at.connyduck.pixelcat.db.AccountManager import at.connyduck.pixelcat.db.AccountManager
import at.connyduck.pixelcat.db.AppDatabase import at.connyduck.pixelcat.db.AppDatabase
import at.connyduck.pixelcat.db.entitity.AccountEntity
import at.connyduck.pixelcat.db.entitity.StatusEntity import at.connyduck.pixelcat.db.entitity.StatusEntity
import at.connyduck.pixelcat.network.FediverseApi import at.connyduck.pixelcat.network.FediverseApi
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class TimelineViewModel @Inject constructor( class TimelineViewModel @Inject constructor(
// private val repository: TimelineRepo, // private val repository: TimelineRepo,
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val db: AppDatabase, private val db: AppDatabase,
fediverseApi: FediverseApi fediverseApi: FediverseApi
): ViewModel() { ) : ViewModel() {
private val accountId = MutableLiveData<Long>() private val accountId = MutableLiveData<Long>()
@ -38,8 +41,7 @@ class TimelineViewModel @Inject constructor(
fun onFavorite(status: StatusEntity) { fun onFavorite(status: StatusEntity) {
viewModelScope.launch { viewModelScope.launch {
// repository.onFavorite(status, accountManager.activeAccount()?.id!!) // repository.onFavorite(status, accountManager.activeAccount()?.id!!)
} }
} }
} }

View File

@ -6,8 +6,9 @@ class Loading<T> (override val data: T? = null) : UiState<T>(data)
class Success<T> (override val data: T? = null) : UiState<T>(data) class Success<T> (override val data: T? = null) : UiState<T>(data)
class Error<T> (override val data: T? = null, class Error<T> (
val errorMessage: String? = null, override val data: T? = null,
var consumed: Boolean = false, val errorMessage: String? = null,
val cause: Throwable? = null var consumed: Boolean = false,
): UiState<T>(data) val cause: Throwable? = null
) : UiState<T>(data)

View File

@ -8,8 +8,8 @@ import androidx.annotation.ColorInt
@ColorInt @ColorInt
fun Context.getColorForAttr(@AttrRes attr: Int): Int { fun Context.getColorForAttr(@AttrRes attr: Int): Int {
val value = TypedValue() val value = TypedValue()
if(theme.resolveAttribute(attr, value, true)) { if (theme.resolveAttribute(attr, value, true)) {
return value.data return value.data
} }
throw IllegalStateException("Attribute not found") throw IllegalStateException("Attribute not found")
} }

View File

@ -22,4 +22,4 @@ fun Context.getColorForAttr(@AttrRes attr: Int): Int {
} else { } else {
throw IllegalArgumentException() throw IllegalArgumentException()
} }
} }

View File

@ -13,5 +13,5 @@ fun View.hide() {
var View.visible var View.visible
get() = visibility == View.VISIBLE get() = visibility == View.VISIBLE
set(value) { set(value) {
visibility = if(value) View.VISIBLE else View.GONE visibility = if (value) View.VISIBLE else View.GONE
} }

View File

@ -10,5 +10,4 @@ object Config {
const val oAuthScopes = "read write follow" const val oAuthScopes = "read write follow"
val domainExceptions = arrayOf("gab.com", "gab.ai", "gabfed.com") val domainExceptions = arrayOf("gab.com", "gab.ai", "gabfed.com")
}
}

View File

@ -14,7 +14,7 @@ import dagger.android.ContributesAndroidInjector
@Module @Module
abstract class ActivityModule { abstract class ActivityModule {
//TODO order stuff here // TODO order stuff here
@ContributesAndroidInjector(modules = [FragmentModule::class]) @ContributesAndroidInjector(modules = [FragmentModule::class])
abstract fun contributesMainActivity(): MainActivity abstract fun contributesMainActivity(): MainActivity
@ -38,5 +38,4 @@ abstract class ActivityModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesComposeActivity(): ComposeActivity abstract fun contributesComposeActivity(): ComposeActivity
} }

View File

@ -8,14 +8,16 @@ import dagger.android.AndroidInjector
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Component(modules = [ @Component(
AppModule::class, modules = [
NetworkModule::class, AppModule::class,
AndroidInjectionModule::class, NetworkModule::class,
ActivityModule::class, AndroidInjectionModule::class,
ViewModelModule::class, ActivityModule::class,
ServiceModule::class ViewModelModule::class,
]) ServiceModule::class
]
)
interface AppComponent : AndroidInjector<PixelcatApplication> { interface AppComponent : AndroidInjector<PixelcatApplication> {
@Component.Builder @Component.Builder
@ -27,4 +29,4 @@ interface AppComponent : AndroidInjector<PixelcatApplication> {
} }
override fun inject(app: PixelcatApplication) override fun inject(app: PixelcatApplication)
} }

View File

@ -16,7 +16,7 @@ import javax.inject.Singleton
class AppModule { class AppModule {
@Provides @Provides
fun providesApp(app: PixelcatApplication): Application = app fun providesApp(app: PixelcatApplication): Application = app
@Provides @Provides
fun providesContext(app: Application): Context = app fun providesContext(app: Application): Context = app
@ -39,8 +39,4 @@ class AppModule {
fun providesAccountManager(db: AppDatabase): AccountManager { fun providesAccountManager(db: AppDatabase): AccountManager {
return AccountManager(db) return AccountManager(db)
} }
}
}

View File

@ -21,5 +21,4 @@ abstract class FragmentModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun profileFragment(): ProfileFragment abstract fun profileFragment(): ProfileFragment
}
}

View File

@ -16,7 +16,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.create import retrofit2.create
import java.util.* import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Singleton import javax.inject.Singleton
@ -34,10 +34,12 @@ class NetworkModule {
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
if(BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
okHttpClientBuilder.addInterceptor(HttpLoggingInterceptor().apply { okHttpClientBuilder.addInterceptor(
level = HttpLoggingInterceptor.Level.HEADERS HttpLoggingInterceptor().apply {
}) level = HttpLoggingInterceptor.Level.HEADERS
}
)
} }
return okHttpClientBuilder.build() return okHttpClientBuilder.build()
@ -61,10 +63,9 @@ class NetworkModule {
.addCallAdapterFactory(NetworkResponseAdapterFactory()) .addCallAdapterFactory(NetworkResponseAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi)) .addConverterFactory(MoshiConverterFactory.create(moshi))
.build() .build()
} }
@Provides @Provides
@Singleton @Singleton
fun providesApi(retrofit: Retrofit): FediverseApi = retrofit.create() fun providesApi(retrofit: Retrofit): FediverseApi = retrofit.create()
} }

View File

@ -9,6 +9,4 @@ abstract class ServiceModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesSendStatusService(): SendStatusService abstract fun contributesSendStatusService(): SendStatusService
} }

View File

@ -47,7 +47,6 @@ abstract class ViewModelModule {
@ViewModelKey(MainViewModel::class) @ViewModelKey(MainViewModel::class)
internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(TimelineViewModel::class) @ViewModelKey(TimelineViewModel::class)
@ -72,5 +71,5 @@ abstract class ViewModelModule {
@IntoMap @IntoMap
@ViewModelKey(ComposeViewModel::class) @ViewModelKey(ComposeViewModel::class)
internal abstract fun composeViewModel(viewModel: ComposeViewModel): ViewModel internal abstract fun composeViewModel(viewModel: ComposeViewModel): ViewModel
//Add more ViewModels here // Add more ViewModels here
} }

View File

@ -1,6 +1,10 @@
package at.connyduck.pixelcat.db package at.connyduck.pixelcat.db
import androidx.room.* import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import at.connyduck.pixelcat.db.entitity.AccountEntity import at.connyduck.pixelcat.db.entitity.AccountEntity
@Dao @Dao
@ -14,5 +18,4 @@ interface AccountDao {
@Query("SELECT * FROM AccountEntity ORDER BY id ASC") @Query("SELECT * FROM AccountEntity ORDER BY id ASC")
suspend fun loadAll(): List<AccountEntity> suspend fun loadAll(): List<AccountEntity>
} }

View File

@ -12,7 +12,7 @@ import at.connyduck.pixelcat.model.Account
* @author ConnyDuck * @author ConnyDuck
*/ */
//TODO check if the comments are up to date // TODO check if the comments are up to date
private const val TAG = "AccountManager" private const val TAG = "AccountManager"
@ -23,9 +23,8 @@ class AccountManager(db: AppDatabase) {
private var accounts: MutableList<AccountEntity> = mutableListOf() private var accounts: MutableList<AccountEntity> = mutableListOf()
private val accountDao: AccountDao = db.accountDao() private val accountDao: AccountDao = db.accountDao()
suspend fun activeAccount(): AccountEntity? { suspend fun activeAccount(): AccountEntity? {
if(activeAccount == null) { if (activeAccount == null) {
accounts = accountDao.loadAll().toMutableList() accounts = accountDao.loadAll().toMutableList()
activeAccount = accounts.find { acc -> activeAccount = accounts.find { acc ->
@ -35,7 +34,6 @@ class AccountManager(db: AppDatabase) {
return activeAccount return activeAccount
} }
/** /**
* Adds a new empty account and makes it the active account. * Adds a new empty account and makes it the active account.
* More account information has to be added later with [updateActiveAccount] * More account information has to be added later with [updateActiveAccount]
@ -60,7 +58,6 @@ class AccountManager(db: AppDatabase) {
auth = authData, auth = authData,
isActive = true isActive = true
) )
} }
/** /**
@ -73,7 +70,6 @@ class AccountManager(db: AppDatabase) {
Log.d(TAG, "saveAccount: saving account with id " + account.id) Log.d(TAG, "saveAccount: saving account with id " + account.id)
accountDao.insertOrReplace(account) accountDao.insertOrReplace(account)
} }
} }
/** /**
@ -97,9 +93,7 @@ class AccountManager(db: AppDatabase) {
activeAccount = null activeAccount = null
} }
return activeAccount return activeAccount
} }
} }
/** /**
@ -113,9 +107,9 @@ class AccountManager(db: AppDatabase) {
it.username = account.username it.username = account.username
it.displayName = account.name it.displayName = account.name
it.profilePictureUrl = account.avatar it.profilePictureUrl = account.avatar
// it.defaultPostPrivacy = account.source?.privacy ?: Status.Visibility.PUBLIC // it.defaultPostPrivacy = account.source?.privacy ?: Status.Visibility.PUBLIC
it.defaultMediaSensitivity = account.source?.sensitive ?: false it.defaultMediaSensitivity = account.source?.sensitive ?: false
// it.emojis = account.emojis ?: emptyList() // it.emojis = account.emojis ?: emptyList()
Log.d(TAG, "updateActiveAccount: saving account with id " + it.id) Log.d(TAG, "updateActiveAccount: saving account with id " + it.id)
it.id = accountDao.insertOrReplace(it) it.id = accountDao.insertOrReplace(it)
@ -123,13 +117,12 @@ class AccountManager(db: AppDatabase) {
val accountIndex = accounts.indexOf(it) val accountIndex = accounts.indexOf(it)
if (accountIndex != -1) { if (accountIndex != -1) {
//in case the user was already logged in with this account, remove the old information // in case the user was already logged in with this account, remove the old information
accounts.removeAt(accountIndex) accounts.removeAt(accountIndex)
accounts.add(accountIndex, it) accounts.add(accountIndex, it)
} else { } else {
accounts.add(it) accounts.add(it)
} }
} }
} }
@ -187,5 +180,4 @@ class AccountManager(db: AppDatabase) {
acc.id == accountId acc.id == accountId
} }
} }
}
}

View File

@ -5,12 +5,10 @@ import androidx.room.RoomDatabase
import at.connyduck.pixelcat.db.entitity.AccountEntity import at.connyduck.pixelcat.db.entitity.AccountEntity
import at.connyduck.pixelcat.db.entitity.StatusEntity import at.connyduck.pixelcat.db.entitity.StatusEntity
@Database(entities = [AccountEntity::class, StatusEntity::class], version = 1) @Database(entities = [AccountEntity::class, StatusEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun accountDao(): AccountDao abstract fun accountDao(): AccountDao
abstract fun statusDao(): TimelineDao abstract fun statusDao(): TimelineDao
}
}

View File

@ -1,13 +1,11 @@
package at.connyduck.pixelcat.db package at.connyduck.pixelcat.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import at.connyduck.pixelcat.model.Attachment import at.connyduck.pixelcat.model.Attachment
import at.connyduck.pixelcat.model.Status import at.connyduck.pixelcat.model.Status
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.Types import com.squareup.moshi.Types
import java.util.Date
import java.util.*
class Converters { class Converters {
@ -25,16 +23,16 @@ class Converters {
@TypeConverter @TypeConverter
fun attachmentListToJson(attachmentList: List<Attachment>?): String { fun attachmentListToJson(attachmentList: List<Attachment>?): String {
val type = Types.newParameterizedType( val type = Types.newParameterizedType(
List::class.java, List::class.java,
Attachment::class.java Attachment::class.java
) )
return moshi.adapter<List<Attachment>>(type).toJson(attachmentList) return moshi.adapter<List<Attachment>>(type).toJson(attachmentList)
} }
@TypeConverter @TypeConverter
fun jsonToAttachmentList(attachmentListJson: String?): List<Attachment>? { fun jsonToAttachmentList(attachmentListJson: String?): List<Attachment>? {
if(attachmentListJson == null) { if (attachmentListJson == null) {
return null return null
} }
val type = Types.newParameterizedType( val type = Types.newParameterizedType(
@ -53,5 +51,4 @@ class Converters {
fun longToDate(date: Long): Date { fun longToDate(date: Long): Date {
return Date(date) return Date(date)
} }
}
}

View File

@ -1,7 +1,11 @@
package at.connyduck.pixelcat.db package at.connyduck.pixelcat.db
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room.* import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import at.connyduck.pixelcat.db.entitity.StatusEntity import at.connyduck.pixelcat.db.entitity.StatusEntity
@Dao @Dao
@ -21,5 +25,4 @@ interface TimelineDao {
@Query("DELETE FROM StatusEntity WHERE accountId = :accountId") @Query("DELETE FROM StatusEntity WHERE accountId = :accountId")
suspend fun clearAll(accountId: Long) suspend fun clearAll(accountId: Long)
} }

View File

@ -2,33 +2,44 @@
package at.connyduck.pixelcat.db.entitity package at.connyduck.pixelcat.db.entitity
import androidx.room.* import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(indices = [Index(value = ["domain", "accountId"], @Entity(
unique = true)]) indices = [
//@TypeConverters(Converters::class) Index(
data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, value = ["domain", "accountId"],
val domain: String, unique = true
@Embedded(prefix = "auth_") var auth: AccountAuthData, )
var isActive: Boolean, ]
var accountId: String = "", )
var username: String = "", // @TypeConverters(Converters::class)
var displayName: String = "", data class AccountEntity(
var profilePictureUrl: String = "", @field:PrimaryKey(autoGenerate = true) var id: Long,
var notificationsEnabled: Boolean = true, val domain: String,
var notificationsMentioned: Boolean = true, @Embedded(prefix = "auth_") var auth: AccountAuthData,
var notificationsFollowed: Boolean = true, var isActive: Boolean,
var notificationsReblogged: Boolean = true, var accountId: String = "",
var notificationsFavorited: Boolean = true, var username: String = "",
var notificationSound: Boolean = true, var displayName: String = "",
var notificationVibration: Boolean = true, var profilePictureUrl: String = "",
var notificationLight: Boolean = true, var notificationsEnabled: Boolean = true,
var defaultMediaSensitivity: Boolean = false, var notificationsMentioned: Boolean = true,
var alwaysShowSensitiveMedia: Boolean = false, var notificationsFollowed: Boolean = true,
var mediaPreviewEnabled: Boolean = true, var notificationsReblogged: Boolean = true,
var lastNotificationId: String = "0", var notificationsFavorited: Boolean = true,
var activeNotifications: String = "[]", var notificationSound: Boolean = true,
var notificationsFilter: String = "[]") { var notificationVibration: Boolean = true,
var notificationLight: Boolean = true,
var defaultMediaSensitivity: Boolean = false,
var alwaysShowSensitiveMedia: Boolean = false,
var mediaPreviewEnabled: Boolean = true,
var lastNotificationId: String = "0",
var activeNotifications: String = "[]",
var notificationsFilter: String = "[]"
) {
val identifier: String val identifier: String
get() = "$domain:$accountId" get() = "$domain:$accountId"

View File

@ -4,24 +4,24 @@ import androidx.room.Entity
import at.connyduck.pixelcat.model.Account import at.connyduck.pixelcat.model.Account
@Entity( @Entity(
primaryKeys = ["serverId", "timelineUserId"] primaryKeys = ["serverId", "timelineUserId"]
) )
data class TimelineAccountEntity( data class TimelineAccountEntity(
val serverId: Long, val serverId: Long,
val id: String, val id: String,
val localUsername: String, val localUsername: String,
val username: String, val username: String,
val displayName: String, val displayName: String,
val url: String, val url: String,
val avatar: String val avatar: String
) )
fun Account.toEntity(serverId: Long) = TimelineAccountEntity( fun Account.toEntity(serverId: Long) = TimelineAccountEntity(
serverId = serverId, serverId = serverId,
id = id, id = id,
localUsername = localUsername, localUsername = localUsername,
username = username, username = username,
displayName = displayName, displayName = displayName,
url = url, url = url,
avatar = avatar avatar = avatar
) )

View File

@ -8,11 +8,9 @@ import androidx.room.TypeConverters
import at.connyduck.pixelcat.db.Converters import at.connyduck.pixelcat.db.Converters
import at.connyduck.pixelcat.model.Attachment import at.connyduck.pixelcat.model.Attachment
import at.connyduck.pixelcat.model.Status import at.connyduck.pixelcat.model.Status
import java.util.* import java.util.Date
@Entity( @Entity(primaryKeys = ["accountId", "id"])
primaryKeys = ["accountId", "id"]
)
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
data class StatusEntity( data class StatusEntity(
val accountId: Long, val accountId: Long,

View File

@ -5,8 +5,8 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class AccessToken( data class AccessToken(
@Json(name = "access_token") val accessToken: String, @Json(name = "access_token") val accessToken: String,
@Json(name = "refresh_token") val refreshToken: String?, @Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "expires_in") val expiresIn: Long?, @Json(name = "expires_in") val expiresIn: Long?,
@Json(name = "created_at") val createdAt: Long? @Json(name = "created_at") val createdAt: Long?
) )

View File

@ -8,7 +8,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parceler import kotlinx.android.parcel.Parceler
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import java.util.* import java.util.Date
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Account( data class Account(
@ -26,8 +26,8 @@ data class Account(
@Json(name = "statuses_count") val statusesCount: Int, @Json(name = "statuses_count") val statusesCount: Int,
val source: AccountSource?, val source: AccountSource?,
val bot: Boolean, val bot: Boolean,
// val emojis: List<Emoji>, // nullable for backward compatibility // val emojis: List<Emoji>, // nullable for backward compatibility
val fields: List<Field>?, //nullable for backward compatibility val fields: List<Field>?, // nullable for backward compatibility
val moved: Account? val moved: Account?
) { ) {
@ -43,26 +43,26 @@ data class Account(
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize @Parcelize
data class AccountSource( data class AccountSource(
// val privacy: Status.Visibility, // val privacy: Status.Visibility,
val sensitive: Boolean, val sensitive: Boolean,
val note: String, val note: String,
val fields: List<StringField>? val fields: List<StringField>?
): Parcelable ) : Parcelable
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize @Parcelize
data class Field ( data class Field(
val name: String, val name: String,
// val value: @WriteWith<SpannedParceler>() Spanned, // val value: @WriteWith<SpannedParceler>() Spanned,
@Json(name = "verified_at") val verifiedAt: Date? @Json(name = "verified_at") val verifiedAt: Date?
): Parcelable ) : Parcelable
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize @Parcelize
data class StringField ( data class StringField(
val name: String, val name: String,
val value: String val value: String
): Parcelable ) : Parcelable
object SpannedParceler : Parceler<Spanned> { object SpannedParceler : Parceler<Spanned> {
override fun create(parcel: Parcel): Spanned = HtmlCompat.fromHtml(parcel.readString() ?: "", HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH) override fun create(parcel: Parcel): Spanned = HtmlCompat.fromHtml(parcel.readString() ?: "", HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH)
@ -70,4 +70,4 @@ object SpannedParceler : Parceler<Spanned> {
override fun Spanned.write(parcel: Parcel, flags: Int) { override fun Spanned.write(parcel: Parcel, flags: Int) {
parcel.writeString(HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)) parcel.writeString(HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL))
} }
} }

View File

@ -5,6 +5,6 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class AppCredentials( data class AppCredentials(
@Json(name = "client_id") val clientId: String, @Json(name = "client_id") val clientId: String,
@Json(name = "client_secret") val clientSecret: String @Json(name = "client_secret") val clientSecret: String
) )

View File

@ -48,9 +48,9 @@ data class Attachment(
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize @Parcelize
data class MetaData ( data class MetaData(
val focus: Focus?, val focus: Focus?,
val duration: Float? val duration: Float?
) : Parcelable ) : Parcelable
/** /**
@ -61,8 +61,8 @@ data class Attachment(
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@Parcelize @Parcelize
data class Focus ( data class Focus(
val x: Float, val x: Float,
val y: Float val y: Float
) : Parcelable ) : Parcelable
} }

View File

@ -6,10 +6,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class NewStatus( data class NewStatus(
val status: String, val status: String,
//@Json(name = "spoiler_text") val warningText: String, // @Json(name = "spoiler_text") val warningText: String,
@Json(name = "in_reply_to_id") val inReplyToId: String?, @Json(name = "in_reply_to_id") val inReplyToId: String?,
val visibility: String, val visibility: String,
val sensitive: Boolean, val sensitive: Boolean,
@Json(name = "media_ids") val mediaIds: List<String>? @Json(name = "media_ids") val mediaIds: List<String>?
) )

View File

@ -4,7 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Relationship ( data class Relationship(
val id: String, val id: String,
val following: Boolean, val following: Boolean,
@Json(name = "followed_by") val followedBy: Boolean, @Json(name = "followed_by") val followedBy: Boolean,

View File

@ -3,7 +3,7 @@ package at.connyduck.pixelcat.model
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import java.util.* import java.util.Date
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Status( data class Status(
@ -62,7 +62,7 @@ data class Status(
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Mention ( data class Mention(
val id: String, val id: String,
val url: String, val url: String,
val acct: String, val acct: String,
@ -70,9 +70,8 @@ data class Status(
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Application ( data class Application(
val name: String, val name: String,
val website: String? val website: String?
) )
} }

View File

@ -1,10 +1,24 @@
package at.connyduck.pixelcat.network package at.connyduck.pixelcat.network
import at.connyduck.pixelcat.model.* import at.connyduck.pixelcat.model.AccessToken
import at.connyduck.pixelcat.model.Account
import at.connyduck.pixelcat.model.AppCredentials
import at.connyduck.pixelcat.model.Attachment
import at.connyduck.pixelcat.model.NewStatus
import at.connyduck.pixelcat.model.Relationship
import at.connyduck.pixelcat.model.Status
import at.connyduck.pixelcat.network.calladapter.NetworkResponse import at.connyduck.pixelcat.network.calladapter.NetworkResponse
import okhttp3.MultipartBody import okhttp3.MultipartBody
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
interface FediverseApi { interface FediverseApi {
@ -26,12 +40,12 @@ interface FediverseApi {
@FormUrlEncoded @FormUrlEncoded
@POST("oauth/token") @POST("oauth/token")
suspend fun fetchOAuthToken( suspend fun fetchOAuthToken(
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Field("client_id") clientId: String, @Field("client_id") clientId: String,
@Field("client_secret") clientSecret: String, @Field("client_secret") clientSecret: String,
@Field("redirect_uri") redirectUri: String, @Field("redirect_uri") redirectUri: String,
@Field("code") code: String, @Field("code") code: String,
@Field("grant_type") grantType: String = "authorization_code" @Field("grant_type") grantType: String = "authorization_code"
): NetworkResponse<AccessToken> ): NetworkResponse<AccessToken>
@FormUrlEncoded @FormUrlEncoded
@ -44,7 +58,6 @@ interface FediverseApi {
@Field("grant_type") grantType: String = "refresh_token" @Field("grant_type") grantType: String = "refresh_token"
): NetworkResponse<AccessToken> ): NetworkResponse<AccessToken>
@GET("api/v1/accounts/verify_credentials") @GET("api/v1/accounts/verify_credentials")
suspend fun accountVerifyCredentials(): NetworkResponse<Account> suspend fun accountVerifyCredentials(): NetworkResponse<Account>
@ -53,7 +66,7 @@ interface FediverseApi {
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("since_id") sinceId: String? = null, @Query("since_id") sinceId: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null
): NetworkResponse<List<Status>> ): NetworkResponse<List<Status>>
@GET("api/v1/accounts/{id}/statuses") @GET("api/v1/accounts/{id}/statuses")
suspend fun accountTimeline( suspend fun accountTimeline(
@ -141,4 +154,4 @@ interface FediverseApi {
suspend fun unfavouriteStatus( suspend fun unfavouriteStatus(
@Path("id") statusId: String @Path("id") statusId: String
): NetworkResponse<Status> ): NetworkResponse<Status>
} }

View File

@ -27,7 +27,7 @@ class InstanceSwitchAuthInterceptor(private val accountManager: AccountManager)
builder.url(swapHost(originalRequest.url, instanceHeader)) builder.url(swapHost(originalRequest.url, instanceHeader))
builder.removeHeader(FediverseApi.DOMAIN_HEADER) builder.removeHeader(FediverseApi.DOMAIN_HEADER)
} else if (currentAccount != null) { } else if (currentAccount != null) {
//use domain of current account // use domain of current account
builder.url(swapHost(originalRequest.url, currentAccount.domain)) builder.url(swapHost(originalRequest.url, currentAccount.domain))
.header( .header(
"Authorization", "Authorization",
@ -37,7 +37,6 @@ class InstanceSwitchAuthInterceptor(private val accountManager: AccountManager)
val newRequest = builder.build() val newRequest = builder.build()
return chain.proceed(newRequest) return chain.proceed(newRequest)
} else { } else {
return chain.proceed(originalRequest) return chain.proceed(originalRequest)
} }
@ -46,6 +45,4 @@ class InstanceSwitchAuthInterceptor(private val accountManager: AccountManager)
private fun swapHost(url: HttpUrl, host: String): HttpUrl { private fun swapHost(url: HttpUrl, host: String): HttpUrl {
return url.newBuilder().host(host).build() return url.newBuilder().host(host).build()
} }
} }

View File

@ -10,13 +10,10 @@ import okhttp3.Route
class RefreshTokenAuthenticator(private val accountManager: AccountManager) : Authenticator { class RefreshTokenAuthenticator(private val accountManager: AccountManager) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? { override fun authenticate(route: Route?, response: Response): Request? {
val currentAccount = runBlocking { accountManager.activeAccount() } val currentAccount = runBlocking { accountManager.activeAccount() }
// TODO // TODO
return null return null
} }
} }

View File

@ -5,7 +5,7 @@ import at.connyduck.pixelcat.BuildConfig
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
class UserAgentInterceptor: Interceptor { class UserAgentInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val requestWithUserAgent = chain.request() val requestWithUserAgent = chain.request()
@ -14,5 +14,4 @@ class UserAgentInterceptor: Interceptor {
.build() .build()
return chain.proceed(requestWithUserAgent) return chain.proceed(requestWithUserAgent)
} }
}
}

View File

@ -13,4 +13,4 @@ class NetworkCallAdapter<S : Any>(
override fun adapt(call: Call<S>): Call<NetworkResponse<S>> { override fun adapt(call: Call<S>): Call<NetworkResponse<S>> {
return NetworkResponseCall(call) return NetworkResponseCall(call)
} }
} }

View File

@ -14,7 +14,7 @@ sealed class NetworkResponse<out A : Any> {
} }
} }
sealed class NetworkResponseError: Throwable() { sealed class NetworkResponseError : Throwable() {
data class ApiError(val code: Int) : NetworkResponseError() data class ApiError(val code: Int) : NetworkResponseError()
@ -27,4 +27,4 @@ sealed class NetworkResponseError: Throwable() {
* For example, json parsing error * For example, json parsing error
*/ */
data class UnknownError(val error: Throwable?) : NetworkResponseError() data class UnknownError(val error: Throwable?) : NetworkResponseError()
} }

View File

@ -38,4 +38,4 @@ class NetworkResponseAdapterFactory : CallAdapter.Factory() {
return NetworkCallAdapter<Any>(successBodyType) return NetworkCallAdapter<Any>(successBodyType)
} }
} }

View File

@ -28,21 +28,37 @@ internal class NetworkResponseCall<S : Any>(
// Response is successful but the body is null // Response is successful but the body is null
callback.onResponse( callback.onResponse(
this@NetworkResponseCall, this@NetworkResponseCall,
Response.success(NetworkResponse.Failure(NetworkResponseError.ApiError(response.code()))) Response.success(
NetworkResponse.Failure(
NetworkResponseError.ApiError(
response.code()
)
)
)
) )
} }
} else { } else {
callback.onResponse( callback.onResponse(
this@NetworkResponseCall, this@NetworkResponseCall,
Response.success(NetworkResponse.Failure(NetworkResponseError.ApiError(response.code()))) Response.success(
NetworkResponse.Failure(
NetworkResponseError.ApiError(
response.code()
)
)
) )
)
} }
} }
override fun onFailure(call: Call<S>, throwable: Throwable) { override fun onFailure(call: Call<S>, throwable: Throwable) {
Log.d("NetworkResponseCall", "Network response failed", throwable) Log.d("NetworkResponseCall", "Network response failed", throwable)
val networkResponse = when (throwable) { val networkResponse = when (throwable) {
is IOException -> NetworkResponse.Failure(NetworkResponseError.NetworkError(throwable)) is IOException -> NetworkResponse.Failure(
NetworkResponseError.NetworkError(
throwable
)
)
else -> NetworkResponse.Failure(NetworkResponseError.UnknownError(throwable)) else -> NetworkResponse.Failure(NetworkResponseError.UnknownError(throwable))
} }
callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse)) callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
@ -65,4 +81,4 @@ internal class NetworkResponseCall<S : Any>(
override fun request(): Request = delegate.request() override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout() override fun timeout(): Timeout = delegate.timeout()
} }

View File

@ -7,14 +7,13 @@ import java.lang.IllegalStateException
inline fun <reified T> Fragment.arg(key: String): T { inline fun <reified T> Fragment.arg(key: String): T {
val value = arguments?.get(key) val value = arguments?.get(key)
if(value !is T) { if (value !is T) {
throw IllegalStateException("Argument $key is of wrong type") throw IllegalStateException("Argument $key is of wrong type")
} }
return value return value
} }
inline fun Fragment.withArgs(argsBuilder: Bundle.() -> Unit): Fragment { inline fun Fragment.withArgs(argsBuilder: Bundle.() -> Unit): Fragment {
this.arguments = Bundle().apply(argsBuilder) this.arguments = Bundle().apply(argsBuilder)
return this return this
} }

View File

@ -17,10 +17,10 @@ import kotlin.reflect.KProperty
*/ */
inline fun <T : ViewBinding> AppCompatActivity.viewBinding( inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
crossinline bindingInflater: (LayoutInflater) -> T) = crossinline bindingInflater: (LayoutInflater) -> T
lazy(LazyThreadSafetyMode.NONE) { ) = lazy(LazyThreadSafetyMode.NONE) {
bindingInflater(layoutInflater) bindingInflater(layoutInflater)
} }
class FragmentViewBindingDelegate<T : ViewBinding>( class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment, val fragment: Fragment,
@ -31,14 +31,16 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
init { init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observe(fragment, fragment.viewLifecycleOwnerLiveData.observe(
fragment,
Observer { t -> Observer { t ->
t?.lifecycle?.addObserver(object : DefaultLifecycleObserver { t?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) { override fun onDestroy(owner: LifecycleOwner) {
binding = null binding = null
} }
}) })
}) }
)
} }
}) })
} }
@ -59,4 +61,4 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
} }
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory) FragmentViewBindingDelegate(this, viewBindingFactory)

View File

@ -11,14 +11,13 @@ buildscript {
} }
} }
apply(plugin = "org.jlleitschuh.gradle.ktlint")
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven(url = "https://jitpack.io") maven(url = "https://jitpack.io")
} }
apply(plugin = "org.jlleitschuh.gradle.ktlint")
} }
tasks.register("clean", Delete::class.java) { tasks.register("clean", Delete::class.java) {