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 {
viewBinding = true
}
}
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 {
val lifecycleVersion = "2.3.0-alpha04"
@ -94,7 +98,7 @@ dependencies {
implementation("com.google.dagger:dagger:2.28")
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")

View File

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

View File

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

View File

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

View File

@ -10,9 +10,9 @@ import com.google.android.material.card.MaterialCardView
class LicenseCard
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialCardView(context, attrs, defStyleAttr) {
private val binding =
@ -31,14 +31,11 @@ class LicenseCard
binding.licenseCardName.text = name
binding.licenseCardLicense.text = license
if(link.isNullOrBlank()) {
if (link.isNullOrBlank()) {
binding.licenseCardLink.hide()
} else {
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) :
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 kotlinx.coroutines.launch
class AccountSelectionBottomSheet(
private val accountManager: AccountManager
) : BottomSheetDialogFragment() {
@ -22,7 +21,6 @@ class AccountSelectionBottomSheet(
private val binding
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -50,7 +48,7 @@ class AccountSelectionBottomSheet(
}
private fun onNewAccount() {
//TODO don't create intent here
// TODO don't create intent here
startActivity(Intent(requireContext(), LoginActivity::class.java))
}
@ -58,5 +56,4 @@ class AccountSelectionBottomSheet(
super.onDestroyView()
_binding = null
}
}
}

View File

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

View File

@ -18,7 +18,7 @@ import com.fxn.pix.Pix
import com.google.android.material.bottomsheet.BottomSheetBehavior
import javax.inject.Inject
class ComposeActivity: BaseActivity() {
class ComposeActivity : BaseActivity() {
@Inject
lateinit var viewModelFactory: ViewModelFactory
@ -35,7 +35,6 @@ class ComposeActivity: BaseActivity() {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.root.setOnApplyWindowInsetsListener { _, insets ->
val top = insets.systemWindowInsetTop
@ -45,11 +44,10 @@ class ComposeActivity: BaseActivity() {
insets.consumeSystemWindowInsets()
}
if(viewModel.images.value.isNullOrEmpty()) {
if (viewModel.images.value.isNullOrEmpty()) {
viewModel.addImage(intent.getStringExtra(EXTRA_MEDIA_URI)!!)
}
setSupportActionBar(binding.composeToolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -75,23 +73,27 @@ class ComposeActivity: BaseActivity() {
changeVisibility(VISIBILITY.FOLLOWERS_ONLY)
}
viewModel.images.observe(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
viewModel.images.observe(
this,
Observer {
adapter.submitList(it)
}
)
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?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_PICK_MEDIA) {
@ -99,7 +101,6 @@ class ComposeActivity: BaseActivity() {
data?.getStringArrayListExtra(Pix.IMAGE_RESULTS)
Log.e("Result", returnValue.toString())
viewModel.addImage(returnValue?.first()!!)
}
}
@ -108,7 +109,6 @@ class ComposeActivity: BaseActivity() {
visibilityBottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
}
companion object {
private const val REQUEST_CODE_PICK_MEDIA = 123
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) :
RecyclerView.ViewHolder(binding.root)
RecyclerView.ViewHolder(binding.root)

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import at.connyduck.pixelcat.components.settings.AppSettings
import dagger.android.support.DaggerAppCompatActivity
import javax.inject.Inject
abstract class BaseActivity: DaggerAppCompatActivity() {
abstract class BaseActivity : DaggerAppCompatActivity() {
@Inject
lateinit var appSettings: AppSettings
@ -15,10 +15,8 @@ abstract class BaseActivity: DaggerAppCompatActivity() {
super.onCreate(savedInstanceState)
theme.applyStyle(appSettings.getAppColorStyle(), true)
if(!appSettings.useSystemFont()) {
if (!appSettings.useSystemFont()) {
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 javax.inject.Inject
class LoginActivity : BaseActivity(), Observer<LoginModel> {
@Inject
@ -58,7 +57,7 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
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)
return
}
@ -72,7 +71,7 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
when (item.itemId) {
R.id.navigation_settings -> {
startActivity(SettingsActivity.newIntent(this))
return true
@ -89,17 +88,16 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
override fun onChanged(loginModel: LoginModel?) {
binding.loginInput.setText(loginModel?.input)
if(loginModel == null) {
if (loginModel == null) {
return
}
when(loginModel.state) {
when (loginModel.state) {
LoginState.NO_ERROR -> binding.loginInputLayout.error = null
LoginState.AUTH_ERROR -> binding.loginInputLayout.error = "auth error"
LoginState.INVALID_DOMAIN -> binding.loginInputLayout.error = "invalid domain"
LoginState.NETWORK_ERROR -> binding.loginInputLayout.error = "network error"
LoginState.LOADING -> {
}
LoginState.SUCCESS -> {
startActivityForResult(LoginWebViewActivity.newIntent(loginModel.domain!!, loginModel.clientId!!, loginModel.clientSecret!!, this), REQUEST_CODE)
@ -114,5 +112,4 @@ class LoginActivity : BaseActivity(), Observer<LoginModel> {
companion object {
private const val REQUEST_CODE = 14
}
}

View File

@ -10,10 +10,8 @@ data class LoginModel(
val domain: String? = null,
val clientId: 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
}

View File

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

View File

@ -12,7 +12,6 @@ import at.connyduck.pixelcat.config.Config
import android.webkit.WebViewClient
import at.connyduck.pixelcat.databinding.ActivityLoginWebViewBinding
class LoginWebViewActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginWebViewBinding
@ -36,10 +35,10 @@ class LoginWebViewActivity : AppCompatActivity() {
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 {
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())
return true
}
@ -48,7 +47,6 @@ class LoginWebViewActivity : AppCompatActivity() {
}
}
binding.loginWebView.loadUrl(url)
}
private fun loginSuccess(authCode: String) {
@ -59,7 +57,6 @@ class LoginWebViewActivity : AppCompatActivity() {
finish()
}
private fun toQueryString(parameters: Map<String, String>): String {
return parameters.map { "${it.key}=${Uri.encode(it.value)}" }
.joinToString("&")

View File

@ -61,7 +61,6 @@ class MainActivity : BaseActivity() {
true
}
else -> false
}
}
@ -87,7 +86,6 @@ class MainActivity : BaseActivity() {
binding.navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
mainViewModel.whatever()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -100,7 +98,4 @@ class MainActivity : BaseActivity() {
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.timeline.TimelineFragment
class MainFragmentAdapter(fragmentActivity: FragmentActivity): FragmentStateAdapter(fragmentActivity) {
class MainFragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun createFragment(position: Int): Fragment {
return when(position) {
return when (position) {
0 -> TimelineFragment.newInstance()
1 -> SearchFragment.newInstance()
2 -> NotificationsFragment.newInstance()
@ -23,5 +23,4 @@ class MainFragmentAdapter(fragmentActivity: FragmentActivity): FragmentStateAdap
}
override fun getItemCount() = 4
}
}

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ class GridSpacingItemDecoration(
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view) // item position
if(position < topOffset) return
if (position < topOffset) return
val column = (position - topOffset) % spanCount // item column
@ -28,7 +28,5 @@ class GridSpacingItemDecoration(
if (position - topOffset >= spanCount) {
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.databinding.ActivityProfileBinding
class ProfileActivity: BaseActivity() {
class ProfileActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -30,7 +30,6 @@ class ProfileActivity: BaseActivity() {
add(R.id.layoutContainer, ProfileFragment.newInstance(intent.getStringExtra(EXTRA_ACCOUNT_ID)))
}
}
}
companion object {
@ -42,4 +41,4 @@ class ProfileActivity: BaseActivity() {
}
}
}
}
}

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import coil.api.load
import coil.transform.RoundedCornersTransformation
import java.text.NumberFormat
class ProfileHeaderAdapter: RecyclerView.Adapter<ProfileHeaderViewHolder>() {
class ProfileHeaderAdapter : RecyclerView.Adapter<ProfileHeaderViewHolder>() {
private var account: Account? = null
private var isSelf: Boolean = false
@ -61,7 +61,7 @@ class ProfileHeaderAdapter: RecyclerView.Adapter<ProfileHeaderViewHolder>() {
}
if (payloads.isEmpty() || payloads.contains(RELATIONSHIP_CHANGED)) {
relationship?.let {
if(it.following) {
if (it.following) {
holder.binding.profileFollowButton.setText(R.string.profile_follows_you)
} else {
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(
private val imageSizePx: Int
): PagedListAdapter<Status, ProfileImageViewHolder>(
object: DiffUtil.ItemCallback<Status>() {
) : PagedListAdapter<Status, ProfileImageViewHolder>(
object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(old: Status, new: Status): Boolean {
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(
private val fediverseApi: FediverseApi,
private val accountManager: AccountManager
): ViewModel() {
) : ViewModel() {
val profile = MutableLiveData<UiState<Account>>()
val relationship = MutableLiveData<UiState<Relationship>>()
val profileImages = MutableLiveData<PagedList<Status>>()
val profileImages = MutableLiveData<PagedList<Status>>()
val isSelf: Boolean
get() = accountId == null
@ -46,11 +46,14 @@ class ProfileViewModel @Inject constructor(
private fun loadAccount(reload: Boolean = false) {
if (profile.value == null || reload) {
viewModelScope.launch {
fediverseApi.account(getAccountId()).fold({
profile.value = Success(it)
}, {
profile.value = Error(cause = it)
})
fediverseApi.account(getAccountId()).fold(
{
profile.value = Success(it)
},
{
profile.value = Error(cause = it)
}
)
}
}
}
@ -58,24 +61,28 @@ class ProfileViewModel @Inject constructor(
private fun loadRelationship(reload: Boolean = false) {
if (relationship.value == null || reload) {
viewModelScope.launch {
fediverseApi.relationships(listOf(getAccountId())).fold({
relationship.value = Success(it.first())
}, {
relationship.value = Error(cause = it)
})
fediverseApi.relationships(listOf(getAccountId())).fold(
{
relationship.value = Success(it.first())
},
{
relationship.value = Error(cause = it)
}
)
}
}
}
private fun loadImages(reload: Boolean = false) {
if(profileImages.value == null || reload) {
if (profileImages.value == null || reload) {
profileImages.value = PagedList.Builder(
ProfileImageDataSource(
fediverseApi,
accountId,
accountManager,
viewModelScope
), 20
),
20
).setNotifyExecutor(Executors.mainThreadExecutor())
.setFetchExecutor(java.util.concurrent.Executors.newSingleThreadExecutor())
.build()
@ -85,5 +92,4 @@ class ProfileViewModel @Inject constructor(
private suspend fun getAccountId(): String {
return accountId ?: accountManager.activeAccount()?.accountId!!
}
}
}

View File

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

View File

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

View File

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

View File

@ -48,11 +48,10 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
preferences.registerOnSharedPreferenceChangeListener(this)
restartActivitiesOnExit = intent.getBooleanExtra(EXTRA_RESTART_ACTIVITIES, false)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when(key) {
when (key) {
getString(R.string.key_pref_app_color) -> restartCurrentActivity()
getString(R.string.key_pref_night_mode) -> AppCompatDelegate.setDefaultNightMode(appSettings.getNightMode())
}
@ -68,7 +67,7 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
override fun onBackPressed() {
val parentActivityName = intent.getStringExtra(EXTRA_PARENT_ACTIVITY)
if(restartActivitiesOnExit && parentActivityName != null) {
if (restartActivitiesOnExit && parentActivityName != null) {
val restartIntent = Intent()
restartIntent.component = ComponentName(this, intent.getStringExtra(EXTRA_PARENT_ACTIVITY)!!)
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 javax.inject.Inject
class SplashActivity: DaggerAppCompatActivity() {
class SplashActivity : DaggerAppCompatActivity() {
@Inject
lateinit var accountManager: AccountManager
@ -24,7 +24,7 @@ class SplashActivity: DaggerAppCompatActivity() {
Intent(
this@SplashActivity,
MainActivity::class.java
) //TODO don't create intents here
) // TODO don't create intents here
} else {
Intent(this@SplashActivity, LoginActivity::class.java)
}
@ -32,4 +32,4 @@ class SplashActivity: DaggerAppCompatActivity() {
finish()
}
}
}
}

View File

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

View File

@ -7,7 +7,7 @@ import at.connyduck.pixelcat.databinding.ItemTimelineImageBinding
import at.connyduck.pixelcat.model.Attachment
import coil.api.load
class TimelineImageAdapter: RecyclerView.Adapter<TimelineImageViewHolder>() {
class TimelineImageAdapter : RecyclerView.Adapter<TimelineImageViewHolder>() {
var images: List<Attachment> = emptyList()
set(value) {
@ -25,10 +25,7 @@ class TimelineImageAdapter: RecyclerView.Adapter<TimelineImageViewHolder>() {
override fun onBindViewHolder(holder: TimelineImageViewHolder, position: Int) {
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)
}
object TimelineDiffUtil: DiffUtil.ItemCallback<StatusEntity>() {
object TimelineDiffUtil : DiffUtil.ItemCallback<StatusEntity>() {
override fun areItemsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean {
return oldItem.id == newItem.id
}
@ -28,7 +28,6 @@ object TimelineDiffUtil: DiffUtil.ItemCallback<StatusEntity>() {
override fun areContentsTheSame(oldItem: StatusEntity, newItem: StatusEntity): Boolean {
return oldItem == newItem
}
}
class TimelineListAdapter(
@ -83,22 +82,17 @@ class TimelineListAdapter(
holder.binding.postDescription.text = status.content.parseAsHtml().trim()
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 {
binding.postImages.adapter = TimelineImageAdapter()
binding.postIndicator.setViewPager(binding.postImages)
(binding.postImages.adapter as TimelineImageAdapter).registerAdapterDataObserver(binding.postIndicator.adapterDataObserver)
// val snapHelper = PagerSnapHelper()
// snapHelper.attachToRecyclerView(binding.postImages)
// val snapHelper = PagerSnapHelper()
// snapHelper.attachToRecyclerView(binding.postImages)
}
}

View File

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

View File

@ -1,22 +1,25 @@
package at.connyduck.pixelcat.components.timeline
import androidx.lifecycle.*
import androidx.paging.*
import androidx.lifecycle.MutableLiveData
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.AppDatabase
import at.connyduck.pixelcat.db.entitity.AccountEntity
import at.connyduck.pixelcat.db.entitity.StatusEntity
import at.connyduck.pixelcat.network.FediverseApi
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import javax.inject.Inject
class TimelineViewModel @Inject constructor(
// private val repository: TimelineRepo,
// private val repository: TimelineRepo,
private val accountManager: AccountManager,
private val db: AppDatabase,
fediverseApi: FediverseApi
): ViewModel() {
) : ViewModel() {
private val accountId = MutableLiveData<Long>()
@ -38,8 +41,7 @@ class TimelineViewModel @Inject constructor(
fun onFavorite(status: StatusEntity) {
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 Error<T> (override val data: T? = null,
val errorMessage: String? = null,
var consumed: Boolean = false,
val cause: Throwable? = null
): UiState<T>(data)
class Error<T> (
override val data: T? = null,
val errorMessage: String? = null,
var consumed: Boolean = false,
val cause: Throwable? = null
) : UiState<T>(data)

View File

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

View File

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

View File

@ -13,5 +13,5 @@ fun View.hide() {
var View.visible
get() = visibility == View.VISIBLE
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"
val domainExceptions = arrayOf("gab.com", "gab.ai", "gabfed.com")
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,6 @@ abstract class ViewModelModule {
@ViewModelKey(MainViewModel::class)
internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(TimelineViewModel::class)
@ -72,5 +71,5 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(ComposeViewModel::class)
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
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
@Dao
@ -14,5 +18,4 @@ interface AccountDao {
@Query("SELECT * FROM AccountEntity ORDER BY id ASC")
suspend fun loadAll(): List<AccountEntity>
}

View File

@ -12,7 +12,7 @@ import at.connyduck.pixelcat.model.Account
* @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"
@ -23,9 +23,8 @@ class AccountManager(db: AppDatabase) {
private var accounts: MutableList<AccountEntity> = mutableListOf()
private val accountDao: AccountDao = db.accountDao()
suspend fun activeAccount(): AccountEntity? {
if(activeAccount == null) {
if (activeAccount == null) {
accounts = accountDao.loadAll().toMutableList()
activeAccount = accounts.find { acc ->
@ -35,7 +34,6 @@ class AccountManager(db: AppDatabase) {
return activeAccount
}
/**
* Adds a new empty account and makes it the active account.
* More account information has to be added later with [updateActiveAccount]
@ -60,7 +58,6 @@ class AccountManager(db: AppDatabase) {
auth = authData,
isActive = true
)
}
/**
@ -73,7 +70,6 @@ class AccountManager(db: AppDatabase) {
Log.d(TAG, "saveAccount: saving account with id " + account.id)
accountDao.insertOrReplace(account)
}
}
/**
@ -97,9 +93,7 @@ class AccountManager(db: AppDatabase) {
activeAccount = null
}
return activeAccount
}
}
/**
@ -113,9 +107,9 @@ class AccountManager(db: AppDatabase) {
it.username = account.username
it.displayName = account.name
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.emojis = account.emojis ?: emptyList()
// it.emojis = account.emojis ?: emptyList()
Log.d(TAG, "updateActiveAccount: saving account with id " + it.id)
it.id = accountDao.insertOrReplace(it)
@ -123,13 +117,12 @@ class AccountManager(db: AppDatabase) {
val accountIndex = accounts.indexOf(it)
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.add(accountIndex, it)
} else {
accounts.add(it)
}
}
}
@ -187,5 +180,4 @@ class AccountManager(db: AppDatabase) {
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.StatusEntity
@Database(entities = [AccountEntity::class, StatusEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun accountDao(): AccountDao
abstract fun statusDao(): TimelineDao
}
}

View File

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

View File

@ -1,7 +1,11 @@
package at.connyduck.pixelcat.db
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
@Dao
@ -21,5 +25,4 @@ interface TimelineDao {
@Query("DELETE FROM StatusEntity WHERE accountId = :accountId")
suspend fun clearAll(accountId: Long)
}

View File

@ -2,33 +2,44 @@
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"],
unique = true)])
//@TypeConverters(Converters::class)
data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
val domain: String,
@Embedded(prefix = "auth_") var auth: AccountAuthData,
var isActive: Boolean,
var accountId: String = "",
var username: String = "",
var displayName: String = "",
var profilePictureUrl: String = "",
var notificationsEnabled: Boolean = true,
var notificationsMentioned: Boolean = true,
var notificationsFollowed: Boolean = true,
var notificationsReblogged: Boolean = true,
var notificationsFavorited: Boolean = true,
var notificationSound: Boolean = true,
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 = "[]") {
@Entity(
indices = [
Index(
value = ["domain", "accountId"],
unique = true
)
]
)
// @TypeConverters(Converters::class)
data class AccountEntity(
@field:PrimaryKey(autoGenerate = true) var id: Long,
val domain: String,
@Embedded(prefix = "auth_") var auth: AccountAuthData,
var isActive: Boolean,
var accountId: String = "",
var username: String = "",
var displayName: String = "",
var profilePictureUrl: String = "",
var notificationsEnabled: Boolean = true,
var notificationsMentioned: Boolean = true,
var notificationsFollowed: Boolean = true,
var notificationsReblogged: Boolean = true,
var notificationsFavorited: Boolean = true,
var notificationSound: Boolean = true,
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
get() = "$domain:$accountId"

View File

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

View File

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

View File

@ -5,8 +5,8 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AccessToken(
@Json(name = "access_token") val accessToken: String,
@Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "expires_in") val expiresIn: Long?,
@Json(name = "created_at") val createdAt: Long?
@Json(name = "access_token") val accessToken: String,
@Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "expires_in") val expiresIn: 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 kotlinx.android.parcel.Parceler
import kotlinx.android.parcel.Parcelize
import java.util.*
import java.util.Date
@JsonClass(generateAdapter = true)
data class Account(
@ -26,8 +26,8 @@ data class Account(
@Json(name = "statuses_count") val statusesCount: Int,
val source: AccountSource?,
val bot: Boolean,
// val emojis: List<Emoji>, // nullable for backward compatibility
val fields: List<Field>?, //nullable for backward compatibility
// val emojis: List<Emoji>, // nullable for backward compatibility
val fields: List<Field>?, // nullable for backward compatibility
val moved: Account?
) {
@ -43,26 +43,26 @@ data class Account(
@JsonClass(generateAdapter = true)
@Parcelize
data class AccountSource(
// val privacy: Status.Visibility,
val sensitive: Boolean,
val note: String,
val fields: List<StringField>?
): Parcelable
// val privacy: Status.Visibility,
val sensitive: Boolean,
val note: String,
val fields: List<StringField>?
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class Field (
val name: String,
// val value: @WriteWith<SpannedParceler>() Spanned,
@Json(name = "verified_at") val verifiedAt: Date?
): Parcelable
data class Field(
val name: String,
// val value: @WriteWith<SpannedParceler>() Spanned,
@Json(name = "verified_at") val verifiedAt: Date?
) : Parcelable
@JsonClass(generateAdapter = true)
@Parcelize
data class StringField (
val name: String,
val value: String
): Parcelable
data class StringField(
val name: String,
val value: String
) : Parcelable
object SpannedParceler : Parceler<Spanned> {
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) {
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)
data class AppCredentials(
@Json(name = "client_id") val clientId: String,
@Json(name = "client_secret") val clientSecret: String
@Json(name = "client_id") val clientId: String,
@Json(name = "client_secret") val clientSecret: String
)

View File

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

View File

@ -6,10 +6,9 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class NewStatus(
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?,
val visibility: String,
val sensitive: Boolean,
@Json(name = "media_ids") val mediaIds: List<String>?
)

View File

@ -4,7 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Relationship (
data class Relationship(
val id: String,
val following: 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.JsonClass
import java.util.*
import java.util.Date
@JsonClass(generateAdapter = true)
data class Status(
@ -62,7 +62,7 @@ data class Status(
}
@JsonClass(generateAdapter = true)
data class Mention (
data class Mention(
val id: String,
val url: String,
val acct: String,
@ -70,9 +70,8 @@ data class Status(
)
@JsonClass(generateAdapter = true)
data class Application (
data class Application(
val name: String,
val website: String?
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -13,4 +13,4 @@ class NetworkCallAdapter<S : Any>(
override fun adapt(call: Call<S>): Call<NetworkResponse<S>> {
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()
@ -27,4 +27,4 @@ sealed class NetworkResponseError: Throwable() {
* For example, json parsing error
*/
data class UnknownError(val error: Throwable?) : NetworkResponseError()
}
}

View File

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

View File

@ -28,21 +28,37 @@ internal class NetworkResponseCall<S : Any>(
// Response is successful but the body is null
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Failure(NetworkResponseError.ApiError(response.code())))
Response.success(
NetworkResponse.Failure(
NetworkResponseError.ApiError(
response.code()
)
)
)
)
}
} else {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Failure(NetworkResponseError.ApiError(response.code())))
callback.onResponse(
this@NetworkResponseCall,
Response.success(
NetworkResponse.Failure(
NetworkResponseError.ApiError(
response.code()
)
)
)
)
}
}
override fun onFailure(call: Call<S>, throwable: Throwable) {
Log.d("NetworkResponseCall", "Network response failed", 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))
}
callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
@ -65,4 +81,4 @@ internal class NetworkResponseCall<S : Any>(
override fun request(): Request = delegate.request()
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 {
val value = arguments?.get(key)
if(value !is T) {
if (value !is T) {
throw IllegalStateException("Argument $key is of wrong type")
}
return value
}
inline fun Fragment.withArgs(argsBuilder: Bundle.() -> Unit): Fragment {
this.arguments = Bundle().apply(argsBuilder)
return this
}
}

View File

@ -17,10 +17,10 @@ import kotlin.reflect.KProperty
*/
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
crossinline bindingInflater: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
bindingInflater(layoutInflater)
}
crossinline bindingInflater: (LayoutInflater) -> T
) = lazy(LazyThreadSafetyMode.NONE) {
bindingInflater(layoutInflater)
}
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
@ -31,14 +31,16 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observe(fragment,
fragment.viewLifecycleOwnerLiveData.observe(
fragment,
Observer { t ->
t?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
})
}
)
}
})
}
@ -59,4 +61,4 @@ class FragmentViewBindingDelegate<T : ViewBinding>(
}
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 {
repositories {
google()
jcenter()
maven(url = "https://jitpack.io")
}
apply(plugin = "org.jlleitschuh.gradle.ktlint")
}
tasks.register("clean", Delete::class.java) {