refactor: Break navigation dependency cycles with :core:navigation (#305)

The previous code generally started an activity by having the activity
provide a method in a companion object that returns the relevant intent,
possibly taking additional parameters that will be included in the
intent as extras.

E.g., if A wants to start B, B provides the method that returns the
intent that starts B.

This introduces a dependency between A and B.

This is worse if B also wants to start A.

For example, if A is `StatusListActivity` and B is`ViewThreadActivity`.
The user might click a status in `StatusListActivity` to view the
thread, starting `ViewThreadActivity`. But from the thread they might
click a hashtag to view the list of statuses with that hashtag. Now
`StatusListActivity` and `ViewThreadActivity` have a circular
dependency.

Even if that doesn't happen the dependency means that any changes to B
will trigger a rebuild of A, even if the changes to B are not relevant.

Break this dependency by adding a `:core:navigation` module with an
`app.pachli.core.navigation` package that contains `Intent` subclasses
that should be used instead. The `quadrant` plugin is used to generate
constants that can be used to launch activities by name instead of by
class, breaking the dependency chain.

The plugin uses the `Activity` names from the manifest, so when an
activity is moved in the future the constant will automatically update
to reflect the new package name.

If the activity's intent requires specific extras those are passed via
the constructor, with companion object methods to extract them from the
intent.

Using the intent classes from this package is enforced by a lint
`IntentDetector` which will warn if any intents are created using a
class literal.

See #291
This commit is contained in:
Nik Clayton 2023-12-07 18:36:00 +01:00 committed by GitHub
parent b14972cb73
commit 1214cf7c8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1299 additions and 638 deletions

View File

@ -135,6 +135,7 @@ dependencies {
implementation(projects.core.accounts)
implementation(projects.core.common)
implementation(projects.core.database)
implementation(projects.core.navigation)
implementation(projects.core.network)
implementation(projects.core.preferences)

View File

@ -872,7 +872,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="479"
line="484"
column="9"/>
</issue>
@ -2049,7 +2049,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/AboutActivity.kt"
line="74"
line="75"
column="9"/>
</issue>
@ -2060,7 +2060,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/AboutActivity.kt"
line="75"
line="76"
column="9"/>
</issue>
@ -2071,7 +2071,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/AboutActivity.kt"
line="76"
line="77"
column="9"/>
</issue>
@ -2082,7 +2082,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="367"
line="372"
column="29"/>
</issue>
@ -2093,7 +2093,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="370"
line="375"
column="29"/>
</issue>
@ -2104,7 +2104,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="376"
line="381"
column="21"/>
</issue>
@ -2115,7 +2115,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="377"
line="382"
column="21"/>
</issue>
@ -2126,7 +2126,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="379"
line="384"
column="21"/>
</issue>
@ -2137,7 +2137,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/account/AccountActivity.kt"
line="389"
line="394"
column="21"/>
</issue>
@ -2148,7 +2148,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/accountlist/AccountListFragment.kt"
line="138"
line="145"
column="17"/>
</issue>
@ -2159,7 +2159,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt"
line="308"
line="318"
column="29"/>
</issue>
@ -2170,7 +2170,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/preference/AccountPreferencesFragment.kt"
line="314"
line="324"
column="25"/>
</issue>
@ -2335,7 +2335,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/compose/ComposeActivity.kt"
line="547"
line="549"
column="21"/>
</issue>
@ -2346,7 +2346,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/compose/ComposeActivity.kt"
line="556"
line="558"
column="17"/>
</issue>
@ -2357,7 +2357,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/compose/ComposeActivity.kt"
line="556"
line="558"
column="17"/>
</issue>
@ -2445,7 +2445,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/filters/EditFilterActivity.kt"
line="116"
line="119"
column="17"/>
</issue>
@ -2456,7 +2456,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/filters/EditFilterActivity.kt"
line="116"
line="119"
column="17"/>
</issue>
@ -2467,7 +2467,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/filters/EditFilterActivity.kt"
line="126"
line="129"
column="17"/>
</issue>
@ -2478,7 +2478,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/filters/EditFilterActivity.kt"
line="126"
line="129"
column="17"/>
</issue>
@ -2731,7 +2731,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/ListsActivity.kt"
line="265"
line="264"
column="21"/>
</issue>
@ -2742,7 +2742,7 @@
errorLine2=" ~~~~~~">
<location
file="src/main/java/app/pachli/ListsActivity.kt"
line="267"
line="266"
column="21"/>
</issue>
@ -2775,7 +2775,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginActivity.kt"
line="156"
line="161"
column="22"/>
</issue>
@ -2786,7 +2786,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="143"
line="144"
column="17"/>
</issue>
@ -2797,7 +2797,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="143"
line="144"
column="17"/>
</issue>
@ -2808,7 +2808,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="152"
line="153"
column="17"/>
</issue>
@ -2819,7 +2819,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="152"
line="153"
column="17"/>
</issue>
@ -2830,7 +2830,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="173"
line="174"
column="25"/>
</issue>
@ -2841,7 +2841,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="173"
line="174"
column="25"/>
</issue>
@ -2852,7 +2852,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="176"
line="177"
column="25"/>
</issue>
@ -2863,7 +2863,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/login/LoginWebViewActivity.kt"
line="176"
line="177"
column="25"/>
</issue>
@ -2874,7 +2874,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="245"
line="252"
column="37"/>
</issue>
@ -2885,7 +2885,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="249"
line="256"
column="37"/>
</issue>
@ -2896,7 +2896,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="353"
line="360"
column="25"/>
</issue>
@ -2907,7 +2907,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="354"
line="361"
column="29"/>
</issue>
@ -2918,7 +2918,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="356"
line="363"
column="25"/>
</issue>
@ -2929,7 +2929,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="357"
line="364"
column="29"/>
</issue>
@ -2940,7 +2940,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="639"
line="646"
column="17"/>
</issue>
@ -2951,7 +2951,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="642"
line="649"
column="21"/>
</issue>
@ -2962,7 +2962,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="647"
line="654"
column="17"/>
</issue>
@ -2973,7 +2973,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="651"
line="658"
column="21"/>
</issue>
@ -2984,7 +2984,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="656"
line="663"
column="17"/>
</issue>
@ -2995,7 +2995,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="659"
line="666"
column="21"/>
</issue>
@ -3006,7 +3006,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="664"
line="671"
column="17"/>
</issue>
@ -3017,29 +3017,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="667"
column="21"/>
</issue>
<issue
id="SyntheticAccessor"
message="Access to `private` method `primaryDrawerItem` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" primaryDrawerItem {"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="672"
column="17"/>
</issue>
<issue
id="SyntheticAccessor"
message="Access to `private` method `setOnClick` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" onClick = {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="675"
line="674"
column="21"/>
</issue>
@ -3072,7 +3050,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="687"
line="686"
column="17"/>
</issue>
@ -3083,7 +3061,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="690"
line="689"
column="21"/>
</issue>
@ -3105,18 +3083,18 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="698"
line="697"
column="21"/>
</issue>
<issue
id="SyntheticAccessor"
message="Access to `private` method `secondaryDrawerItem` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" secondaryDrawerItem {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
message="Access to `private` method `primaryDrawerItem` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" primaryDrawerItem {"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="707"
line="701"
column="17"/>
</issue>
@ -3127,7 +3105,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="710"
line="705"
column="21"/>
</issue>
@ -3138,7 +3116,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="715"
line="714"
column="17"/>
</issue>
@ -3149,7 +3127,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="718"
line="717"
column="21"/>
</issue>
@ -3160,7 +3138,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="723"
line="725"
column="17"/>
</issue>
@ -3171,7 +3149,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="726"
line="728"
column="21"/>
</issue>
@ -3182,7 +3160,29 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="731"
line="733"
column="17"/>
</issue>
<issue
id="SyntheticAccessor"
message="Access to `private` method `setOnClick` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" onClick = {"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="736"
column="21"/>
</issue>
<issue
id="SyntheticAccessor"
message="Access to `private` method `secondaryDrawerItem` of class `MainActivityKt` requires synthetic accessor"
errorLine1=" secondaryDrawerItem {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="741"
column="17"/>
</issue>
@ -3193,7 +3193,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="734"
line="744"
column="21"/>
</issue>
@ -3204,7 +3204,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="741"
line="751"
column="21"/>
</issue>
@ -3215,7 +3215,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="744"
line="754"
column="25"/>
</issue>
@ -3226,7 +3226,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="753"
line="763"
column="17"/>
</issue>
@ -3237,7 +3237,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="756"
line="766"
column="21"/>
</issue>
@ -3248,7 +3248,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="769"
line="779"
column="17"/>
</issue>
@ -3259,7 +3259,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="773"
line="783"
column="21"/>
</issue>
@ -3270,7 +3270,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="898"
line="908"
column="17"/>
</issue>
@ -3281,7 +3281,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="900"
line="910"
column="17"/>
</issue>
@ -3292,7 +3292,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/MainActivity.kt"
line="911"
line="921"
column="17"/>
</issue>
@ -3534,7 +3534,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/SFragment.kt"
line="225"
line="224"
column="48"/>
</issue>
@ -3644,7 +3644,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/timeline/TimelineFragment.kt"
line="196"
line="195"
column="47"/>
</issue>
@ -3699,7 +3699,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/ViewMediaActivity.kt"
line="125"
line="127"
column="21"/>
</issue>
@ -3710,7 +3710,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/ViewMediaActivity.kt"
line="125"
line="127"
column="45"/>
</issue>
@ -3721,7 +3721,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/ViewMediaActivity.kt"
line="125"
line="127"
column="45"/>
</issue>
@ -3732,7 +3732,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/ViewMediaActivity.kt"
line="155"
line="157"
column="45"/>
</issue>
@ -3743,7 +3743,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/ViewMediaActivity.kt"
line="200"
line="202"
column="25"/>
</issue>

View File

@ -3,7 +3,6 @@ package app.pachli
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.SpannableString
@ -16,6 +15,8 @@ import android.widget.Toast
import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.core.navigation.LicenseActivityIntent
import app.pachli.core.navigation.PrivacyPolicyActivityIntent
import app.pachli.databinding.ActivityAboutBinding
import app.pachli.util.NoUnderlineURLSpan
import app.pachli.util.hide
@ -76,8 +77,7 @@ class AboutActivity : BottomSheetActivity() {
binding.aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
binding.aboutPrivacyPolicyTextView.setOnClickListener {
val intent = Intent(this, PrivacyPolicyActivity::class.java)
startActivity(intent)
startActivity(PrivacyPolicyActivityIntent(this))
}
binding.appProfileButton.setOnClickListener {
@ -85,7 +85,7 @@ class AboutActivity : BottomSheetActivity() {
}
binding.aboutLicensesButton.setOnClickListener {
startActivityWithSlideInAnimation(Intent(this, LicenseActivity::class.java))
startActivityWithSlideInAnimation(LicenseActivityIntent(this))
}
binding.copyDeviceInfo.setOnClickListener {

View File

@ -34,9 +34,9 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import app.pachli.MainActivity.Companion.redirectIntent
import app.pachli.adapter.AccountSelectionAdapter
import app.pachli.components.login.LoginActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.PrefKeys.APP_THEME
import app.pachli.core.preferences.SharedPreferencesRepository
@ -182,7 +182,7 @@ abstract class BaseActivity : AppCompatActivity() {
private fun redirectIfNotLoggedIn() {
val account = accountManager.activeAccount
if (account == null) {
val intent = Intent(this, LoginActivity::class.java)
val intent = LoginActivityIntent(this)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivityWithSlideInAnimation(intent)
finish()

View File

@ -17,15 +17,14 @@
package app.pachli
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.lifecycleScope
import app.pachli.components.account.AccountActivity
import app.pachli.components.viewthread.ViewThreadActivity
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.ViewThreadActivityIntent
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.util.looksLikeMastodonUrl
import app.pachli.util.openLink
@ -104,15 +103,13 @@ abstract class BottomSheetActivity : BaseActivity() {
open fun viewThread(statusId: String, url: String?) {
if (!isSearching()) {
val intent = Intent(this, ViewThreadActivity::class.java)
intent.putExtra("id", statusId)
intent.putExtra("url", url)
val intent = ViewThreadActivityIntent(this, statusId, url)
startActivityWithSlideInAnimation(intent)
}
}
open fun viewAccount(id: String) {
val intent = AccountActivity.getIntent(this, id)
val intent = AccountActivityIntent(this, id)
startActivityWithSlideInAnimation(intent)
}

View File

@ -18,8 +18,6 @@
package app.pachli
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -36,6 +34,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.network.model.MastoList
import app.pachli.databinding.ActivityListsBinding
import app.pachli.databinding.DialogListBinding
@ -191,7 +190,7 @@ class ListsActivity : BaseActivity() {
private fun onListSelected(listId: String, listTitle: String) {
startActivityWithSlideInAnimation(
StatusListActivity.newListIntent(this, listId, listTitle),
StatusListActivityIntent.list(this, listId, listTitle),
)
}
@ -277,8 +276,4 @@ class ListsActivity : BaseActivity() {
viewModel.updateList(listId, name, exclusive)
}
}
companion object {
fun newIntent(context: Context) = Intent(context, ListsActivity::class.java)
}
}

View File

@ -46,7 +46,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.IntentCompat
import androidx.core.content.edit
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.content.res.ResourcesCompat
@ -61,24 +60,32 @@ import app.pachli.appstore.CacheUpdater
import app.pachli.appstore.EventHub
import app.pachli.appstore.MainTabsChangedEvent
import app.pachli.appstore.ProfileEditedEvent
import app.pachli.components.account.AccountActivity
import app.pachli.components.accountlist.AccountListActivity
import app.pachli.components.announcements.AnnouncementsActivity
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.compose.ComposeActivity.Companion.canHandleMimeType
import app.pachli.components.drafts.DraftsActivity
import app.pachli.components.login.LoginActivity
import app.pachli.components.notifications.createNotificationChannelsForAccount
import app.pachli.components.notifications.disableAllNotifications
import app.pachli.components.notifications.enablePushNotificationsWithFallback
import app.pachli.components.notifications.notificationsAreEnabled
import app.pachli.components.notifications.showMigrationNoticeIfNecessary
import app.pachli.components.preference.PreferencesActivity
import app.pachli.components.scheduled.ScheduledStatusActivity
import app.pachli.components.search.SearchActivity
import app.pachli.components.trending.TrendingActivity
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TabKind
import app.pachli.core.navigation.AboutActivityIntent
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AnnouncementsActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.DraftsActivityIntent
import app.pachli.core.navigation.EditProfileActivityIntent
import app.pachli.core.navigation.ListActivityIntent
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.navigation.MainActivityIntent
import app.pachli.core.navigation.PreferencesActivityIntent
import app.pachli.core.navigation.PreferencesActivityIntent.PreferenceScreen
import app.pachli.core.navigation.ScheduledStatusActivityIntent
import app.pachli.core.navigation.SearchActivityIntent
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.navigation.TrendingActivityIntent
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.Notification
import app.pachli.core.preferences.PrefKeys
@ -253,13 +260,13 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
)
}
} else if (openDrafts) {
val intent = DraftsActivity.newIntent(this)
val intent = DraftsActivityIntent(this)
startActivity(intent)
} else if (accountRequested && intent.hasExtra(NOTIFICATION_TYPE)) {
// user clicked a notification, show follow requests for type FOLLOW_REQUEST,
// otherwise show notification tab
if (intent.getSerializableExtra(NOTIFICATION_TYPE) == Notification.Type.FOLLOW_REQUEST) {
val intent = AccountListActivity.newIntent(this, AccountListActivity.Type.FOLLOW_REQUESTS)
val intent = AccountListActivityIntent(this, AccountListActivityIntent.Kind.FOLLOW_REQUESTS)
startActivityWithSlideInAnimation(intent)
} else {
showNotificationTab = true
@ -272,7 +279,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
glide = Glide.with(this)
binding.composeButton.setOnClickListener {
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
val composeIntent = ComposeActivityIntent(applicationContext)
startActivity(composeIntent)
}
@ -393,7 +400,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
override fun onMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_search -> {
startActivity(SearchActivity.getIntent(this@MainActivity))
startActivity(SearchActivityIntent(this@MainActivity))
true
}
else -> super.onOptionsItemSelected(item)
@ -503,7 +510,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
return true
}
KeyEvent.KEYCODE_SEARCH -> {
startActivityWithSlideInAnimation(SearchActivity.getIntent(this))
startActivityWithSlideInAnimation(SearchActivityIntent(this))
return true
}
}
@ -512,7 +519,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
when (keyCode) {
KeyEvent.KEYCODE_N -> {
// open compose activity by pressing SHIFT + N (or CTRL + N)
val composeIntent = Intent(applicationContext, ComposeActivity::class.java)
val composeIntent = ComposeActivityIntent(applicationContext)
startActivity(composeIntent)
return true
}
@ -533,12 +540,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
}
private fun forwardToComposeActivity(intent: Intent) {
val composeOptions = IntentCompat.getParcelableExtra(intent, COMPOSE_OPTIONS, ComposeActivity.ComposeOptions::class.java)
val composeOptions = ComposeActivityIntent.getOptions(intent)
val composeIntent = if (composeOptions != null) {
ComposeActivity.startIntent(this, composeOptions)
ComposeActivityIntent(this, composeOptions)
} else {
Intent(this, ComposeActivity::class.java).apply {
ComposeActivityIntent(this).apply {
action = intent.action
type = intent.type
putExtras(intent)
@ -640,7 +647,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_edit_profile
iconicsIcon = GoogleMaterial.Icon.gmd_person
onClick = {
val intent = Intent(context, EditProfileActivity::class.java)
val intent = EditProfileActivityIntent(context)
startActivityWithSlideInAnimation(intent)
}
},
@ -649,7 +656,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
isSelectable = false
iconicsIcon = GoogleMaterial.Icon.gmd_star
onClick = {
val intent = StatusListActivity.newFavouritesIntent(context)
val intent = StatusListActivityIntent.favourites(context)
startActivityWithSlideInAnimation(intent)
}
},
@ -657,7 +664,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_view_bookmarks
iconicsIcon = GoogleMaterial.Icon.gmd_bookmark
onClick = {
val intent = StatusListActivity.newBookmarksIntent(context)
val intent = StatusListActivityIntent.bookmarks(context)
startActivityWithSlideInAnimation(intent)
}
},
@ -665,7 +672,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_view_follow_requests
iconicsIcon = GoogleMaterial.Icon.gmd_person_add
onClick = {
val intent = AccountListActivity.newIntent(context, AccountListActivity.Type.FOLLOW_REQUESTS)
val intent = AccountListActivityIntent(context, AccountListActivityIntent.Kind.FOLLOW_REQUESTS)
startActivityWithSlideInAnimation(intent)
}
},
@ -673,14 +680,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_lists
iconicsIcon = GoogleMaterial.Icon.gmd_list
onClick = {
startActivityWithSlideInAnimation(ListsActivity.newIntent(context))
startActivityWithSlideInAnimation(ListActivityIntent(context))
}
},
primaryDrawerItem {
nameRes = R.string.action_access_drafts
iconRes = R.drawable.ic_notebook
onClick = {
val intent = DraftsActivity.newIntent(context)
val intent = DraftsActivityIntent(context)
startActivityWithSlideInAnimation(intent)
}
},
@ -688,7 +695,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_access_scheduled_posts
iconRes = R.drawable.ic_access_time
onClick = {
startActivityWithSlideInAnimation(ScheduledStatusActivity.newIntent(context))
startActivityWithSlideInAnimation(ScheduledStatusActivityIntent(context))
}
},
primaryDrawerItem {
@ -696,7 +703,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.title_announcements
iconRes = R.drawable.ic_bullhorn_24dp
onClick = {
startActivityWithSlideInAnimation(AnnouncementsActivity.newIntent(context))
startActivityWithSlideInAnimation(AnnouncementsActivityIntent(context))
}
badgeStyle = BadgeStyle().apply {
textColor = ColorHolder.fromColor(MaterialColors.getColor(binding.mainDrawer, com.google.android.material.R.attr.colorOnPrimary))
@ -708,7 +715,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_view_account_preferences
iconRes = R.drawable.ic_account_settings
onClick = {
val intent = PreferencesActivity.newIntent(context, PreferencesActivity.ACCOUNT_PREFERENCES)
val intent = PreferencesActivityIntent(context, PreferenceScreen.ACCOUNT)
startActivityWithSlideInAnimation(intent)
}
},
@ -716,7 +723,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_view_preferences
iconicsIcon = GoogleMaterial.Icon.gmd_settings
onClick = {
val intent = PreferencesActivity.newIntent(context, PreferencesActivity.GENERAL_PREFERENCES)
val intent = PreferencesActivityIntent(context, PreferenceScreen.GENERAL)
startActivityWithSlideInAnimation(intent)
}
},
@ -724,7 +731,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.about_title_activity
iconicsIcon = GoogleMaterial.Icon.gmd_info
onClick = {
val intent = Intent(context, AboutActivity::class.java)
val intent = AboutActivityIntent(context)
startActivityWithSlideInAnimation(intent)
}
},
@ -742,7 +749,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.action_search
iconicsIcon = GoogleMaterial.Icon.gmd_search
onClick = {
startActivityWithSlideInAnimation(SearchActivity.getIntent(context))
startActivityWithSlideInAnimation(SearchActivityIntent(context))
}
},
)
@ -754,7 +761,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
nameRes = R.string.title_public_trending
iconicsIcon = GoogleMaterial.Icon.gmd_trending_up
onClick = {
startActivityWithSlideInAnimation(TrendingActivity.getIntent(context))
startActivityWithSlideInAnimation(TrendingActivityIntent(context))
}
},
)
@ -941,13 +948,15 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
// open profile when active image was clicked
if (current && activeAccount != null) {
val intent = AccountActivity.getIntent(this, activeAccount.accountId)
val intent = AccountActivityIntent(this, activeAccount.accountId)
startActivityWithSlideInAnimation(intent)
return
}
// open LoginActivity to add new account
if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) {
startActivityWithSlideInAnimation(LoginActivity.getIntent(this, LoginActivity.MODE_ADDITIONAL_LOGIN))
startActivityWithSlideInAnimation(
LoginActivityIntent(this, LoginMode.ADDITIONAL_LOGIN),
)
return
}
// change Account
@ -958,7 +967,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
private fun changeAccount(newSelectedId: Long, forward: Intent?) {
cacheUpdater.stop()
accountManager.setActiveAccount(newSelectedId)
val intent = Intent(this, MainActivity::class.java)
val intent = MainActivityIntent(this)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (forward != null) {
intent.type = forward.type
@ -988,9 +997,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
lifecycleScope.launch {
val otherAccountAvailable = logoutUsecase.logout()
val intent = if (otherAccountAvailable) {
Intent(this@MainActivity, MainActivity::class.java)
MainActivityIntent(this@MainActivity)
} else {
LoginActivity.getIntent(this@MainActivity, LoginActivity.MODE_DEFAULT)
LoginActivityIntent(this@MainActivity, LoginMode.DEFAULT)
}
startActivity(intent)
finishWithoutSlideOutAnimation()
@ -1197,7 +1206,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
*/
@JvmStatic
fun accountSwitchIntent(context: Context, pachliAccountId: Long): Intent {
return Intent(context, MainActivity::class.java).apply {
return MainActivityIntent(context).apply {
putExtra(PACHLI_ACCOUNT_ID, pachliAccountId)
}
}
@ -1221,7 +1230,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider {
@JvmStatic
fun composeIntent(
context: Context,
options: ComposeActivity.ComposeOptions,
options: ComposeOptions,
pachliAccountId: Long = -1,
notificationTag: String? = null,
notificationId: Int = -1,

View File

@ -18,12 +18,13 @@
package app.pachli
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import app.pachli.components.login.LoginActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.navigation.MainActivityIntent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -41,9 +42,9 @@ class SplashActivity : AppCompatActivity() {
/** Determine whether the user is currently logged in, and if so go ahead and load the
* timeline. Otherwise, start the activity_login screen. */
val intent = if (accountManager.activeAccount != null) {
Intent(this, MainActivity::class.java)
MainActivityIntent(this)
} else {
LoginActivity.getIntent(this, LoginActivity.MODE_DEFAULT)
LoginActivityIntent(this, LoginMode.DEFAULT)
}
startActivity(intent)
finish()

View File

@ -17,8 +17,6 @@
package app.pachli
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -26,8 +24,10 @@ import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import app.pachli.appstore.EventHub
import app.pachli.appstore.FilterChangedEvent
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.timeline.TimelineFragment
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.FilterV1
import app.pachli.core.network.model.TimelineKind
@ -84,7 +84,7 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
setSupportActionBar(binding.includedToolbar.toolbar)
timelineKind = intent.getParcelableExtra(EXTRA_KIND)!!
timelineKind = StatusListActivityIntent.getKind(intent)
val title = when (timelineKind) {
is TimelineKind.Favourites -> getString(R.string.title_favourites)
@ -113,11 +113,11 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
val composeIntent = when (timelineKind) {
is TimelineKind.Tag -> {
val tag = (timelineKind as TimelineKind.Tag).tags.first()
ComposeActivity.startIntent(
ComposeActivityIntent(
this,
ComposeActivity.ComposeOptions(
ComposeOptions(
content = getString(R.string.title_tag_with_initial_position).format(tag),
initialCursorPosition = ComposeActivity.InitialCursorPosition.START,
initialCursorPosition = ComposeOptions.InitialCursorPosition.START,
),
)
}
@ -125,10 +125,7 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
is TimelineKind.Favourites,
is TimelineKind.UserList,
-> {
ComposeActivity.startIntent(
this,
ComposeActivity.ComposeOptions(),
)
ComposeActivityIntent(this, ComposeOptions())
}
else -> null
}
@ -367,29 +364,4 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
return true
}
companion object {
private const val EXTRA_KIND = "kind"
fun newFavouritesIntent(context: Context) =
Intent(context, StatusListActivity::class.java).apply {
putExtra(EXTRA_KIND, TimelineKind.Favourites)
}
fun newBookmarksIntent(context: Context) =
Intent(context, StatusListActivity::class.java).apply {
putExtra(EXTRA_KIND, TimelineKind.Bookmarks)
}
fun newListIntent(context: Context, listId: String, listTitle: String) =
Intent(context, StatusListActivity::class.java).apply {
putExtra(EXTRA_KIND, TimelineKind.UserList(listId, listTitle))
}
@JvmStatic
fun newHashtagIntent(context: Context, hashtag: String) =
Intent(context, StatusListActivity::class.java).apply {
putExtra(EXTRA_KIND, TimelineKind.Tag(listOf(hashtag)))
}
}
}

View File

@ -17,7 +17,6 @@
package app.pachli
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
@ -44,6 +43,7 @@ import app.pachli.appstore.EventHub
import app.pachli.appstore.MainTabsChangedEvent
import app.pachli.core.database.model.TabData
import app.pachli.core.database.model.TabKind
import app.pachli.core.navigation.ListActivityIntent
import app.pachli.core.network.model.MastoList
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.databinding.ActivityTabPreferenceBinding
@ -303,7 +303,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
val dialogBuilder = AlertDialog.Builder(this)
.setTitle(R.string.select_list_title)
.setNeutralButton(R.string.select_list_manage) { _, _ ->
val listIntent = Intent(applicationContext, ListsActivity::class.java)
val listIntent = ListActivityIntent(applicationContext)
startActivity(listIntent)
}
.setNegativeButton(android.R.string.cancel, null)

View File

@ -24,7 +24,6 @@ import android.app.DownloadManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Color
@ -40,13 +39,14 @@ import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.core.content.IntentCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import app.pachli.BuildConfig.APPLICATION_ID
import app.pachli.components.viewthread.ViewThreadActivity
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.navigation.ViewThreadActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.databinding.ActivityViewMediaBinding
import app.pachli.fragment.ViewImageFragment
@ -55,7 +55,6 @@ import app.pachli.pager.ImagePagerAdapter
import app.pachli.pager.SingleImagePagerAdapter
import app.pachli.util.getTemporaryMediaFilename
import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider
import autodispose2.autoDispose
import com.bumptech.glide.Glide
@ -73,6 +72,9 @@ import java.util.Locale
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
/**
* Show one or more media items (pictures, video, audio, etc).
*/
@AndroidEntryPoint
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener, ViewVideoFragment.VideoActionsListener {
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
@ -100,8 +102,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
supportPostponeEnterTransition()
// Gather the parameters.
attachments = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_ATTACHMENTS, AttachmentViewData::class.java)
val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
attachments = ViewMediaActivityIntent.getAttachments(intent)
val initialPosition = ViewMediaActivityIntent.getAttachmentIndex(intent)
// Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
// but it cannot be expressed and if I don't specify type explicitly compilation fails
@ -111,7 +113,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
// Setup the view pager.
ImagePagerAdapter(this, realAttachs, initialPosition)
} else {
imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
imageUrl = ViewMediaActivityIntent.getImageUrl(intent)
?: throw IllegalArgumentException("attachment list or image url has to be set")
SingleImagePagerAdapter(this, imageUrl!!)
@ -241,7 +243,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
private fun onOpenStatus() {
val attach = attachments!![binding.viewPager.currentItem]
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl))
startActivityWithSlideInAnimation(ViewThreadActivityIntent(this, attach.statusId, attach.statusUrl))
}
private fun copyLink() {
@ -344,27 +346,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
shareFile(file, mimeType)
}
companion object {
private const val EXTRA_ATTACHMENTS = "attachments"
private const val EXTRA_ATTACHMENT_INDEX = "index"
private const val EXTRA_SINGLE_IMAGE_URL = "single_image"
@JvmStatic
fun newIntent(context: Context?, attachments: List<AttachmentViewData>, index: Int): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
return intent
}
@JvmStatic
fun newSingleImageIntent(context: Context, url: String): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
return intent
}
}
}
abstract class ViewMediaAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {

View File

@ -20,10 +20,10 @@ import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView
import app.pachli.R
import app.pachli.ViewMediaActivity.Companion.newSingleImageIntent
import app.pachli.core.common.util.AbsoluteTimeFormatter
import app.pachli.core.common.util.formatNumber
import app.pachli.core.database.model.TranslationState
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.PreviewCardKind
@ -877,7 +877,7 @@ abstract class StatusBaseViewHolder protected constructor(itemView: View) :
cardView.bind(card, status.actionable.sensitive, statusDisplayOptions) { target ->
if (card.kind == PreviewCardKind.PHOTO && card.embedUrl.isNotEmpty() && target == PreviewCardView.Target.IMAGE) {
context.startActivity(
newSingleImageIntent(context, card.embedUrl),
ViewMediaActivityIntent(context, card.embedUrl),
)
} else {
listener.onViewUrl(card.url)

View File

@ -47,15 +47,17 @@ import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer
import app.pachli.BottomSheetActivity
import app.pachli.EditProfileActivity
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.ViewMediaActivity
import app.pachli.components.account.list.ListsForAccountFragment
import app.pachli.components.accountlist.AccountListActivity
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.report.ReportActivity
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.EditProfileActivityIntent
import app.pachli.core.navigation.ReportActivityIntent
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.Relationship
import app.pachli.core.network.parseAsMastodonHtml
@ -100,6 +102,9 @@ import java.util.Locale
import javax.inject.Inject
import kotlin.math.abs
/**
* Show a single account's profile details.
*/
@AndroidEntryPoint
class AccountActivity :
BottomSheetActivity(),
@ -172,7 +177,7 @@ class AccountActivity :
addMenuProvider(this)
// Obtain information to fill out the profile.
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
viewModel.setAccountInfo(AccountActivityIntent.getAccountId(intent))
animateAvatar = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
animateEmojis = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
@ -221,12 +226,12 @@ class AccountActivity :
binding.accountFieldList.adapter = accountFieldAdapter
val accountListClickListener = { v: View ->
val type = when (v.id) {
R.id.accountFollowers -> AccountListActivity.Type.FOLLOWERS
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWS
val kind = when (v.id) {
R.id.accountFollowers -> AccountListActivityIntent.Kind.FOLLOWERS
R.id.accountFollowing -> AccountListActivityIntent.Kind.FOLLOWS
else -> throw AssertionError()
}
val accountListIntent = AccountListActivity.newIntent(this, type, viewModel.accountId)
val accountListIntent = AccountListActivityIntent(this, kind, viewModel.accountId)
startActivityWithSlideInAnimation(accountListIntent)
}
binding.accountFollowers.setOnClickListener(accountListClickListener)
@ -540,7 +545,7 @@ class AccountActivity :
private fun viewImage(view: View, uri: String) {
view.transitionName = uri
startActivity(
ViewMediaActivity.newSingleImageIntent(view.context, uri),
ViewMediaActivityIntent(view.context, uri),
ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, uri).toBundle(),
)
}
@ -606,7 +611,7 @@ class AccountActivity :
binding.accountFollowButton.setOnClickListener {
if (viewModel.isSelf) {
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java)
val intent = EditProfileActivityIntent(this@AccountActivity)
startActivity(intent)
return@setOnClickListener
}
@ -879,26 +884,25 @@ class AccountActivity :
private fun mention() {
loadedAccount?.let {
val options = if (viewModel.isSelf) {
ComposeActivity.ComposeOptions(kind = ComposeActivity.ComposeKind.NEW)
ComposeOptions(kind = ComposeOptions.ComposeKind.NEW)
} else {
ComposeActivity.ComposeOptions(
ComposeOptions(
mentionedUsernames = setOf(it.username),
kind = ComposeActivity.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.NEW,
)
}
val intent = ComposeActivity.startIntent(this, options)
val intent = ComposeActivityIntent(this, options)
startActivity(intent)
}
}
override fun onViewTag(tag: String) {
val intent = StatusListActivity.newHashtagIntent(this, tag)
val intent = StatusListActivityIntent.hashtag(this, tag)
startActivityWithSlideInAnimation(intent)
}
override fun onViewAccount(id: String) {
val intent = Intent(this, AccountActivity::class.java)
intent.putExtra("id", id)
val intent = AccountActivityIntent(this, id)
startActivityWithSlideInAnimation(intent)
}
@ -979,7 +983,7 @@ class AccountActivity :
}
R.id.action_report -> {
loadedAccount?.let { loadedAccount ->
startActivity(ReportActivity.getIntent(this, viewModel.accountId, loadedAccount.username))
startActivity(ReportActivityIntent(this, viewModel.accountId, loadedAccount.username))
}
return true
}
@ -999,14 +1003,6 @@ class AccountActivity :
}
companion object {
private const val KEY_ACCOUNT_ID = "id"
private val argbEvaluator = ArgbEvaluator()
@JvmStatic
fun getIntent(context: Context, accountId: String): Intent {
val intent = Intent(context, AccountActivity::class.java)
intent.putExtra(KEY_ACCOUNT_ID, accountId)
return intent
}
}
}

View File

@ -32,8 +32,9 @@ import androidx.paging.LoadState
import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R
import app.pachli.ViewMediaActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
@ -43,7 +44,6 @@ import app.pachli.util.hide
import app.pachli.util.openLink
import app.pachli.util.show
import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData
import com.google.android.material.color.MaterialColors
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
@ -175,7 +175,7 @@ class AccountMediaFragment :
Attachment.Type.VIDEO,
Attachment.Type.AUDIO,
-> {
val intent = ViewMediaActivity.newIntent(context, attachmentsFromSameStatus, currentIndex)
val intent = ViewMediaActivityIntent(requireContext(), attachmentsFromSameStatus, currentIndex)
if (activity != null) {
val url = selected.attachment.url
ViewCompat.setTransitionName(view, url)

View File

@ -11,6 +11,7 @@ import androidx.core.view.setPadding
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import app.pachli.R
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.model.Attachment
import app.pachli.databinding.ItemAccountMediaBinding
import app.pachli.util.BindingHolder
@ -18,7 +19,6 @@ import app.pachli.util.decodeBlurHash
import app.pachli.util.getFormattedDescription
import app.pachli.util.hide
import app.pachli.util.show
import app.pachli.viewdata.AttachmentViewData
import com.bumptech.glide.Glide
import com.google.android.material.color.MaterialColors
import java.util.Random

View File

@ -18,7 +18,7 @@ package app.pachli.components.account.media
import androidx.paging.PagingSource
import androidx.paging.PagingState
import app.pachli.viewdata.AttachmentViewData
import app.pachli.core.navigation.AttachmentViewData
class AccountMediaPagingSource(
private val viewModel: AccountMediaViewModel,

View File

@ -22,8 +22,8 @@ import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import app.pachli.components.timeline.util.ifExpected
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.viewdata.AttachmentViewData
import retrofit2.HttpException
@OptIn(ExperimentalPagingApi::class)

View File

@ -23,8 +23,8 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.viewdata.AttachmentViewData
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

View File

@ -16,18 +16,27 @@
package app.pachli.components.accountlist
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.commit
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AccountListActivityIntent.Kind.BLOCKS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FAVOURITED
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOWERS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOWS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOW_REQUESTS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.MUTES
import app.pachli.core.navigation.AccountListActivityIntent.Kind.REBLOGGED
import app.pachli.databinding.ActivityAccountListBinding
import app.pachli.interfaces.AppBarLayoutHost
import app.pachli.util.viewBinding
import com.google.android.material.appbar.AppBarLayout
import dagger.hilt.android.AndroidEntryPoint
/**
* Show a list of accounts of a particular kind.
*/
@AndroidEntryPoint
class AccountListActivity : BottomSheetActivity(), AppBarLayoutHost {
private val binding: ActivityAccountListBinding by viewBinding(ActivityAccountListBinding::inflate)
@ -35,52 +44,30 @@ class AccountListActivity : BottomSheetActivity(), AppBarLayoutHost {
override val appBarLayout: AppBarLayout
get() = binding.includedToolbar.appbar
enum class Type {
FOLLOWS,
FOLLOWERS,
BLOCKS,
MUTES,
FOLLOW_REQUESTS,
REBLOGGED,
FAVOURITED,
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val type = intent.getSerializableExtra(EXTRA_TYPE) as Type
val id: String? = intent.getStringExtra(EXTRA_ID)
val kind = AccountListActivityIntent.getKind(intent)
val id = AccountListActivityIntent.getId(intent)
setSupportActionBar(binding.includedToolbar.toolbar)
supportActionBar?.apply {
when (type) {
Type.BLOCKS -> setTitle(R.string.title_blocks)
Type.MUTES -> setTitle(R.string.title_mutes)
Type.FOLLOW_REQUESTS -> setTitle(R.string.title_follow_requests)
Type.FOLLOWERS -> setTitle(R.string.title_followers)
Type.FOLLOWS -> setTitle(R.string.title_follows)
Type.REBLOGGED -> setTitle(R.string.title_reblogged_by)
Type.FAVOURITED -> setTitle(R.string.title_favourited_by)
when (kind) {
BLOCKS -> setTitle(R.string.title_blocks)
MUTES -> setTitle(R.string.title_mutes)
FOLLOW_REQUESTS -> setTitle(R.string.title_follow_requests)
FOLLOWERS -> setTitle(R.string.title_followers)
FOLLOWS -> setTitle(R.string.title_follows)
REBLOGGED -> setTitle(R.string.title_reblogged_by)
FAVOURITED -> setTitle(R.string.title_favourited_by)
}
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
supportFragmentManager.commit {
replace(R.id.fragment_container, AccountListFragment.newInstance(type, id))
}
}
companion object {
private const val EXTRA_TYPE = "type"
private const val EXTRA_ID = "id"
fun newIntent(context: Context, type: Type, id: String? = null): Intent {
return Intent(context, AccountListActivity::class.java).apply {
putExtra(EXTRA_TYPE, type)
putExtra(EXTRA_ID, id)
}
replace(R.id.fragment_container, AccountListFragment.newInstance(kind, id))
}
}
}

View File

@ -28,9 +28,6 @@ import app.pachli.BaseActivity
import app.pachli.BottomSheetActivity
import app.pachli.PostLookupFallbackBehavior
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.account.AccountActivity
import app.pachli.components.accountlist.AccountListActivity.Type
import app.pachli.components.accountlist.adapter.AccountAdapter
import app.pachli.components.accountlist.adapter.BlocksAdapter
import app.pachli.components.accountlist.adapter.FollowAdapter
@ -38,6 +35,16 @@ import app.pachli.components.accountlist.adapter.FollowRequestsAdapter
import app.pachli.components.accountlist.adapter.FollowRequestsHeaderAdapter
import app.pachli.components.accountlist.adapter.MutesAdapter
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AccountListActivityIntent.Kind
import app.pachli.core.navigation.AccountListActivityIntent.Kind.BLOCKS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FAVOURITED
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOWERS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOWS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.FOLLOW_REQUESTS
import app.pachli.core.navigation.AccountListActivityIntent.Kind.MUTES
import app.pachli.core.navigation.AccountListActivityIntent.Kind.REBLOGGED
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.network.model.HttpHeaderLink
import app.pachli.core.network.model.Relationship
import app.pachli.core.network.model.TimelineAccount
@ -80,7 +87,7 @@ class AccountListFragment :
private val binding by viewBinding(FragmentAccountListBinding::bind)
private lateinit var type: Type
private lateinit var kind: Kind
private var id: String? = null
private lateinit var scrollListener: EndlessOnScrollListener
@ -90,7 +97,7 @@ class AccountListFragment :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
type = requireArguments().getSerializable(ARG_TYPE) as Type
kind = requireArguments().getSerializable(ARG_KIND) as Kind
id = requireArguments().getString(ARG_ID)
}
@ -112,10 +119,10 @@ class AccountListFragment :
val activeAccount = accountManager.activeAccount!!
adapter = when (type) {
Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
Type.FOLLOW_REQUESTS -> {
adapter = when (kind) {
BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
MUTES -> MutesAdapter(this, animateAvatar, animateEmojis, showBotOverlay)
FOLLOW_REQUESTS -> {
val headerAdapter = FollowRequestsHeaderAdapter(
instanceName = activeAccount.domain,
accountLocked = activeAccount.locked,
@ -151,12 +158,12 @@ class AccountListFragment :
override fun onViewTag(tag: String) {
(activity as BaseActivity?)
?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
?.startActivityWithSlideInAnimation(StatusListActivityIntent.hashtag(requireContext(), tag))
}
override fun onViewAccount(id: String) {
(activity as BaseActivity?)?.let {
val intent = AccountActivity.getIntent(it, id)
val intent = AccountActivityIntent(it, id)
it.startActivityWithSlideInAnimation(intent)
}
}
@ -283,31 +290,31 @@ class AccountListFragment :
}
private suspend fun getFetchCallByListType(fromId: String?): Response<List<TimelineAccount>> {
return when (type) {
Type.FOLLOWS -> {
val accountId = requireId(type, id)
return when (kind) {
FOLLOWS -> {
val accountId = requireId(kind, id)
api.accountFollowing(accountId, fromId)
}
Type.FOLLOWERS -> {
val accountId = requireId(type, id)
FOLLOWERS -> {
val accountId = requireId(kind, id)
api.accountFollowers(accountId, fromId)
}
Type.BLOCKS -> api.blocks(fromId)
Type.MUTES -> api.mutes(fromId)
Type.FOLLOW_REQUESTS -> api.followRequests(fromId)
Type.REBLOGGED -> {
val statusId = requireId(type, id)
BLOCKS -> api.blocks(fromId)
MUTES -> api.mutes(fromId)
FOLLOW_REQUESTS -> api.followRequests(fromId)
REBLOGGED -> {
val statusId = requireId(kind, id)
api.statusRebloggedBy(statusId, fromId)
}
Type.FAVOURITED -> {
val statusId = requireId(type, id)
FAVOURITED -> {
val statusId = requireId(kind, id)
api.statusFavouritedBy(statusId, fromId)
}
}
}
private fun requireId(type: Type, id: String?): String {
return requireNotNull(id) { "id must not be null for type " + type.name }
private fun requireId(kind: Kind, id: String?): String {
return requireNotNull(id) { "id must not be null for kind " + kind.name }
}
private fun fetchAccounts(fromId: String? = null) {
@ -410,13 +417,13 @@ class AccountListFragment :
}
companion object {
private const val ARG_TYPE = "type"
private const val ARG_KIND = "kind"
private const val ARG_ID = "id"
fun newInstance(type: Type, id: String? = null): AccountListFragment {
fun newInstance(kind: Kind, id: String? = null): AccountListFragment {
return AccountListFragment().apply {
arguments = Bundle(3).apply {
putSerializable(ARG_TYPE, type)
putSerializable(ARG_KIND, kind)
putString(ARG_ID, id)
}
}

View File

@ -16,8 +16,6 @@
package app.pachli.components.announcements
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -29,9 +27,9 @@ import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.LinearLayoutManager
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.adapter.EmojiAdapter
import app.pachli.adapter.OnEmojiSelectedListener
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.preferences.PrefKeys
import app.pachli.databinding.ActivityAnnouncementsBinding
import app.pachli.util.Error
@ -185,7 +183,7 @@ class AnnouncementsActivity :
}
override fun onViewTag(tag: String) {
val intent = StatusListActivity.newHashtagIntent(this, tag)
val intent = StatusListActivityIntent.hashtag(this, tag)
startActivityWithSlideInAnimation(intent)
}
@ -196,8 +194,4 @@ class AnnouncementsActivity :
override fun onViewUrl(url: String) {
viewUrl(url)
}
companion object {
fun newIntent(context: Context) = Intent(context, AnnouncementsActivity::class.java)
}
}

View File

@ -19,7 +19,6 @@ package app.pachli.components.compose
import android.Manifest
import android.app.ProgressDialog
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
@ -28,7 +27,6 @@ import android.graphics.PorterDuffColorFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.provider.MediaStore
import android.text.Spanned
import android.text.style.URLSpan
@ -77,10 +75,11 @@ import app.pachli.components.compose.view.ComposeOptionsListener
import app.pachli.components.compose.view.ComposeScheduleView
import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.DraftAttachment
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions.InitialCursorPosition
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.NewPoll
import app.pachli.core.network.model.Status
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.PrefKeys.APP_THEME
@ -116,7 +115,6 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import timber.log.Timber
import java.io.File
import java.io.IOException
@ -125,6 +123,10 @@ import java.util.Locale
import kotlin.math.max
import kotlin.math.min
/**
* Compose a status, either by creating one from scratch, or by editing an existing
* status, draft, or scheduled status.
*/
@AndroidEntryPoint
class ComposeActivity :
BaseActivity(),
@ -233,7 +235,7 @@ class ComposeActivity :
/* If the composer is started up as a reply to another post, override the "starting" state
* based on what the intent from the reply request passes. */
val composeOptions: ComposeOptions? = IntentCompat.getParcelableExtra(intent, COMPOSE_OPTIONS_EXTRA, ComposeOptions::class.java)
val composeOptions: ComposeOptions? = ComposeActivityIntent.getOptions(intent)
viewModel.setup(composeOptions)
setupButtons()
@ -1297,60 +1299,6 @@ class ComposeActivity :
viewModel.updateDescription(localId, description)
}
/**
* Status' kind. This particularly affects how the status is handled if the user
* backs out of the edit.
*/
enum class ComposeKind {
/** Status is new */
NEW,
/** Editing a posted status */
EDIT_POSTED,
/** Editing a status started as an existing draft */
EDIT_DRAFT,
/** Editing an an existing scheduled status */
EDIT_SCHEDULED,
}
/**
* Initial position of the cursor in EditText when the compose button is clicked
* in a hashtag timeline
*/
enum class InitialCursorPosition {
START,
END,
}
@Parcelize
data class ComposeOptions(
// Let's keep fields var until all consumers are Kotlin
var scheduledTootId: String? = null,
var draftId: Int? = null,
var content: String? = null,
var mediaUrls: List<String>? = null,
var mediaDescriptions: List<String>? = null,
var mentionedUsernames: Set<String>? = null,
var inReplyToId: String? = null,
var replyVisibility: Status.Visibility? = null,
var visibility: Status.Visibility? = null,
var contentWarning: String? = null,
var replyingStatusAuthor: String? = null,
var replyingStatusContent: String? = null,
var mediaAttachments: List<Attachment>? = null,
var draftAttachments: List<DraftAttachment>? = null,
var scheduledAt: String? = null,
var sensitive: Boolean? = null,
var poll: NewPoll? = null,
var modifiedInitialState: Boolean? = null,
var language: String? = null,
var statusId: String? = null,
var kind: ComposeKind? = null,
var initialCursorPosition: InitialCursorPosition = InitialCursorPosition.END,
) : Parcelable
companion object {
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
@ -1360,20 +1308,6 @@ class ComposeActivity :
private const val SCHEDULED_TIME_KEY = "SCHEDULE"
private const val CONTENT_WARNING_VISIBLE_KEY = "CONTENT_WARNING_VISIBLE"
/**
* @param options ComposeOptions to configure the ComposeActivity
* @return an Intent to start the ComposeActivity
*/
@JvmStatic
fun startIntent(
context: Context,
options: ComposeOptions,
): Intent {
return Intent(context, ComposeActivity::class.java).apply {
putExtra(COMPOSE_OPTIONS_EXTRA, options)
}
}
fun canHandleMimeType(mimeType: String?): Boolean {
return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain")
}

View File

@ -20,7 +20,6 @@ import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.pachli.components.compose.ComposeActivity.ComposeKind
import app.pachli.components.compose.ComposeActivity.QueuedMedia
import app.pachli.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult
import app.pachli.components.drafts.DraftHelper
@ -29,6 +28,8 @@ import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.components.search.SearchType
import app.pachli.core.accounts.AccountManager
import app.pachli.core.common.string.randomAlphanumericString
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions.ComposeKind
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.NewPoll
@ -412,7 +413,7 @@ class ComposeViewModel @Inject constructor(
}
}
fun setup(composeOptions: ComposeActivity.ComposeOptions?) {
fun setup(composeOptions: ComposeOptions?) {
if (setupComplete) {
return
}

View File

@ -36,10 +36,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.adapter.StatusBaseViewHolder
import app.pachli.appstore.EventHub
import app.pachli.components.account.AccountActivity
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.databinding.FragmentTimelineBinding
@ -52,7 +53,6 @@ import app.pachli.util.hide
import app.pachli.util.show
import app.pachli.util.viewBinding
import app.pachli.util.visible
import app.pachli.viewdata.AttachmentViewData
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration
@ -327,12 +327,12 @@ class ConversationsFragment :
}
override fun onViewAccount(id: String) {
val intent = AccountActivity.getIntent(requireContext(), id)
val intent = AccountActivityIntent(requireContext(), id)
startActivity(intent)
}
override fun onViewTag(tag: String) {
val intent = StatusListActivity.newHashtagIntent(requireContext(), tag)
val intent = StatusListActivityIntent.hashtag(requireContext(), tag)
startActivity(intent)
}

View File

@ -17,7 +17,6 @@
package app.pachli.components.drafts
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.Toast
@ -26,8 +25,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.components.compose.ComposeActivity
import app.pachli.core.database.model.DraftEntity
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.network.parseAsMastodonHtml
import app.pachli.databinding.ActivityDraftsBinding
import app.pachli.db.DraftsAlert
@ -106,7 +106,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
viewModel.getStatus(draft.inReplyToId!!)
.fold(
{ status ->
val composeOptions = ComposeActivity.ComposeOptions(
val composeOptions = ComposeOptions(
draftId = draft.id,
content = draft.content,
contentWarning = draft.contentWarning,
@ -120,12 +120,12 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
scheduledAt = draft.scheduledAt,
language = draft.language,
statusId = draft.statusId,
kind = ComposeActivity.ComposeKind.EDIT_DRAFT,
kind = ComposeOptions.ComposeKind.EDIT_DRAFT,
)
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
startActivity(ComposeActivity.startIntent(context, composeOptions))
startActivity(ComposeActivityIntent(context, composeOptions))
},
{ throwable ->
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
@ -147,7 +147,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
}
private fun openDraftWithoutReply(draft: DraftEntity) {
val composeOptions = ComposeActivity.ComposeOptions(
val composeOptions = ComposeOptions(
draftId = draft.id,
content = draft.content,
contentWarning = draft.contentWarning,
@ -158,10 +158,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
scheduledAt = draft.scheduledAt,
language = draft.language,
statusId = draft.statusId,
kind = ComposeActivity.ComposeKind.EDIT_DRAFT,
kind = ComposeOptions.ComposeKind.EDIT_DRAFT,
)
startActivity(ComposeActivity.startIntent(this, composeOptions))
startActivity(ComposeActivityIntent(this, composeOptions))
}
override fun onDeleteDraft(draft: DraftEntity) {
@ -172,8 +172,4 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
}
.show()
}
companion object {
fun newIntent(context: Context) = Intent(context, DraftsActivity::class.java)
}
}

View File

@ -8,13 +8,13 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.content.IntentCompat
import androidx.core.view.size
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.appstore.EventHub
import app.pachli.core.navigation.EditFilterActivityIntent
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.FilterKeyword
import app.pachli.core.network.retrofit.MastodonApi
@ -32,6 +32,9 @@ import retrofit2.HttpException
import java.util.Date
import javax.inject.Inject
/**
* Edit a single server-side filter.
*/
@AndroidEntryPoint
class EditFilterActivity : BaseActivity() {
@Inject
@ -50,7 +53,7 @@ class EditFilterActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
originalFilter = IntentCompat.getParcelableExtra(intent, FILTER_TO_EDIT, Filter::class.java)
originalFilter = EditFilterActivityIntent.getFilter(intent)
filter = originalFilter ?: Filter("", "", listOf(), null, Filter.Action.WARN.action, listOf())
binding.apply {
contextSwitches = mapOf(
@ -295,8 +298,6 @@ class EditFilterActivity : BaseActivity() {
}
companion object {
const val FILTER_TO_EDIT = "FilterToEdit"
// Mastodon *stores* the absolute date in the filter,
// but create/edit take a number of seconds (relative to the time the operation is posted)
fun getSecondsForDurationIndex(index: Int, context: Context?, default: Date? = null): Int? {

View File

@ -1,12 +1,12 @@
package app.pachli.components.filters
import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.core.navigation.EditFilterActivityIntent
import app.pachli.core.network.model.Filter
import app.pachli.databinding.ActivityFiltersBinding
import app.pachli.util.hide
@ -95,11 +95,7 @@ class FiltersActivity : BaseActivity(), FiltersListener {
}
private fun launchEditFilterActivity(filter: Filter? = null) {
val intent = Intent(this, EditFilterActivity::class.java).apply {
if (filter != null) {
putExtra(EditFilterActivity.FILTER_TO_EDIT, filter)
}
}
val intent = EditFilterActivityIntent(this, filter)
startActivity(intent)
overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}

View File

@ -30,8 +30,9 @@ import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity
import app.pachli.BuildConfig
import app.pachli.MainActivity
import app.pachli.R
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.MainActivityIntent
import app.pachli.core.network.model.AccessToken
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.getNonNullString
@ -48,7 +49,11 @@ import okhttp3.HttpUrl
import timber.log.Timber
import javax.inject.Inject
/** Main login page, the first thing that users see. Has prompt for instance and login button. */
/**
* Main login page, the first thing that users see.
*
* Has prompt for instance and login button.
*/
@AndroidEntryPoint
class LoginActivity : BaseActivity() {
@ -318,7 +323,7 @@ class LoginActivity : BaseActivity() {
newAccount = newAccount,
)
val intent = Intent(this, MainActivity::class.java)
val intent = MainActivityIntent(this)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
@ -343,33 +348,19 @@ class LoginActivity : BaseActivity() {
}
private fun isAdditionalLogin(): Boolean {
return intent.getIntExtra(LOGIN_MODE, MODE_DEFAULT) == MODE_ADDITIONAL_LOGIN
return LoginActivityIntent.getLoginMode(intent) == LoginActivityIntent.LoginMode.ADDITIONAL_LOGIN
}
private fun isAccountMigration(): Boolean {
return intent.getIntExtra(LOGIN_MODE, MODE_DEFAULT) == MODE_MIGRATION
return LoginActivityIntent.getLoginMode(intent) == LoginActivityIntent.LoginMode.MIGRATION
}
companion object {
private const val OAUTH_SCOPES = "read write follow push"
private const val LOGIN_MODE = "LOGIN_MODE"
private const val DOMAIN = "domain"
private const val CLIENT_ID = "clientId"
private const val CLIENT_SECRET = "clientSecret"
const val MODE_DEFAULT = 0
const val MODE_ADDITIONAL_LOGIN = 1
// "Migration" is used to update the OAuth scope granted to the client
const val MODE_MIGRATION = 2
@JvmStatic
fun getIntent(context: Context, mode: Int): Intent {
val loginIntent = Intent(context, LoginActivity::class.java)
loginIntent.putExtra(LOGIN_MODE, mode)
return loginIntent
}
/** Make sure the user-entered text is just a fully-qualified domain name. */
private fun canonicalizeDomain(domain: String): String {
// Strip any schemes out.

View File

@ -39,6 +39,7 @@ import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity
import app.pachli.BuildConfig
import app.pachli.R
import app.pachli.core.navigation.LoginWebViewActivityIntent
import app.pachli.databinding.ActivityLoginWebviewBinding
import app.pachli.util.hide
import app.pachli.util.viewBinding
@ -51,7 +52,7 @@ import timber.log.Timber
/** Contract for starting [LoginWebViewActivity]. */
class OauthLogin : ActivityResultContract<LoginData, LoginResult>() {
override fun createIntent(context: Context, input: LoginData): Intent {
val intent = Intent(context, LoginWebViewActivity::class.java)
val intent = LoginWebViewActivityIntent(context)
intent.putExtra(DATA_EXTRA, input)
return intent
}

View File

@ -16,6 +16,7 @@
*/
package app.pachli.components.notifications
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
@ -44,10 +45,10 @@ import app.pachli.MainActivity
import app.pachli.MainActivity.Companion.composeIntent
import app.pachli.MainActivity.Companion.openNotificationIntent
import app.pachli.R
import app.pachli.components.compose.ComposeActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.network.model.Notification
import app.pachli.core.network.parseAsMastodonHtml
import app.pachli.receiver.SendStatusBroadcastReceiver
@ -382,6 +383,9 @@ private fun getStatusReplyIntent(
}
mentionedUsernames.removeAll(setOf(account.username))
mentionedUsernames = ArrayList(LinkedHashSet(mentionedUsernames))
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val replyIntent = Intent(context, SendStatusBroadcastReceiver::class.java)
.setAction(REPLY_ACTION)
.putExtra(KEY_SENDER_ACCOUNT_ID, account.id)
@ -417,16 +421,17 @@ private fun getStatusComposeIntent(
mentionedUsernames.add(mentionedUsername)
}
}
val composeOptions = ComposeActivity.ComposeOptions()
composeOptions.inReplyToId = inReplyToId
composeOptions.replyVisibility = replyVisibility
composeOptions.contentWarning = contentWarning
composeOptions.replyingStatusAuthor = citedLocalAuthor
composeOptions.replyingStatusContent = citedText
composeOptions.mentionedUsernames = mentionedUsernames
composeOptions.modifiedInitialState = true
composeOptions.language = language
composeOptions.kind = ComposeActivity.ComposeKind.NEW
val composeOptions = ComposeOptions(
inReplyToId = inReplyToId,
replyVisibility = replyVisibility,
contentWarning = contentWarning,
replyingStatusAuthor = citedLocalAuthor,
replyingStatusContent = citedText,
mentionedUsernames = mentionedUsernames,
modifiedInitialState = true,
language = language,
kind = ComposeOptions.ComposeKind.NEW,
)
val composeIntent =
composeIntent(context, composeOptions, account.id, body.id, account.id.toInt())
composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

View File

@ -46,6 +46,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R
import app.pachli.adapter.StatusBaseViewHolder
import app.pachli.components.timeline.TimelineLoadStateAdapter
import app.pachli.core.navigation.AttachmentViewData.Companion.list
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.Status
@ -63,7 +64,6 @@ import app.pachli.util.openLink
import app.pachli.util.show
import app.pachli.util.viewBinding
import app.pachli.util.visible
import app.pachli.viewdata.AttachmentViewData.Companion.list
import app.pachli.viewdata.NotificationViewData
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors

View File

@ -22,9 +22,10 @@ import android.os.Build
import android.view.View
import androidx.appcompat.app.AlertDialog
import app.pachli.R
import app.pachli.components.login.LoginActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.network.model.Notification
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.SharedPreferencesRepository
@ -78,7 +79,12 @@ private fun showMigrationExplanationDialog(
if (currentAccountNeedsMigration(accountManager)) {
setMessage(R.string.dialog_push_notification_migration)
setPositiveButton(R.string.title_migration_relogin) { _, _ ->
context.startActivity(LoginActivity.getIntent(context, LoginActivity.MODE_MIGRATION))
context.startActivity(
LoginActivityIntent(
context,
LoginMode.MIGRATION,
),
)
}
} else {
setMessage(R.string.dialog_push_notification_migration_other_accounts)

View File

@ -24,15 +24,18 @@ import androidx.preference.PreferenceFragmentCompat
import app.pachli.BaseActivity
import app.pachli.BuildConfig
import app.pachli.R
import app.pachli.TabPreferenceActivity
import app.pachli.appstore.EventHub
import app.pachli.components.accountlist.AccountListActivity
import app.pachli.components.filters.FiltersActivity
import app.pachli.components.followedtags.FollowedTagsActivity
import app.pachli.components.instancemute.InstanceListActivity
import app.pachli.components.login.LoginActivity
import app.pachli.components.notifications.currentAccountNeedsMigration
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.FiltersActivityIntent
import app.pachli.core.navigation.FollowedTagsActivityIntent
import app.pachli.core.navigation.InstanceListActivityIntent
import app.pachli.core.navigation.LoginActivityIntent
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.navigation.PreferencesActivityIntent
import app.pachli.core.navigation.PreferencesActivityIntent.PreferenceScreen
import app.pachli.core.navigation.TabPreferenceActivityIntent
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.Status
import app.pachli.core.network.retrofit.MastodonApi
@ -90,7 +93,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_tab_preferences)
setIcon(R.drawable.ic_tabs)
setOnPreferenceClickListener {
val intent = Intent(context, TabPreferenceActivity::class.java)
val intent = TabPreferenceActivityIntent(context)
activity?.startActivity(intent)
activity?.overridePendingTransition(
R.anim.slide_from_right,
@ -104,7 +107,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_followed_hashtags)
setIcon(R.drawable.ic_hashtag)
setOnPreferenceClickListener {
val intent = Intent(context, FollowedTagsActivity::class.java)
val intent = FollowedTagsActivityIntent(context)
activity?.startActivity(intent)
activity?.overridePendingTransition(
R.anim.slide_from_right,
@ -118,8 +121,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.action_view_mutes)
setIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.MUTES)
val intent = AccountListActivityIntent(context, AccountListActivityIntent.Kind.MUTES)
activity?.startActivity(intent)
activity?.overridePendingTransition(
R.anim.slide_from_right,
@ -133,8 +135,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.action_view_blocks)
icon = makeIcon(GoogleMaterial.Icon.gmd_block)
setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java)
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
val intent = AccountListActivityIntent(context, AccountListActivityIntent.Kind.BLOCKS)
activity?.startActivity(intent)
activity?.overridePendingTransition(
R.anim.slide_from_right,
@ -148,7 +149,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_domain_mutes)
setIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener {
val intent = Intent(context, InstanceListActivity::class.java)
val intent = InstanceListActivityIntent(context)
activity?.startActivity(intent)
activity?.overridePendingTransition(
R.anim.slide_from_right,
@ -163,7 +164,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_migration_relogin)
setIcon(R.drawable.ic_logout)
setOnPreferenceClickListener {
val intent = LoginActivity.getIntent(context, LoginActivity.MODE_MIGRATION)
val intent = LoginActivityIntent(context, LoginMode.MIGRATION)
(activity as BaseActivity).startActivityWithSlideInAnimation(intent)
true
}
@ -280,7 +281,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
} else {
activity?.let {
val intent =
PreferencesActivity.newIntent(it, PreferencesActivity.NOTIFICATION_PREFERENCES)
PreferencesActivityIntent(it, PreferenceScreen.NOTIFICATION)
it.startActivity(intent)
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}
@ -346,7 +347,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
}
private fun launchFilterActivity() {
val intent = Intent(context, FiltersActivity::class.java)
val intent = FiltersActivityIntent(requireContext())
activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
}

View File

@ -16,7 +16,6 @@
package app.pachli.components.preference
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
@ -26,9 +25,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import app.pachli.BaseActivity
import app.pachli.MainActivity
import app.pachli.R
import app.pachli.appstore.EventHub
import app.pachli.core.navigation.MainActivityIntent
import app.pachli.core.navigation.PreferencesActivityIntent
import app.pachli.core.navigation.PreferencesActivityIntent.PreferenceScreen
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.PrefKeys.APP_THEME
import app.pachli.core.preferences.getNonNullString
@ -41,6 +42,9 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* Show specific preferences.
*/
@AndroidEntryPoint
class PreferencesActivity :
BaseActivity(),
@ -55,7 +59,7 @@ class PreferencesActivity :
* Either the back stack activities need to all be recreated, or do the easier thing, which
* is hijack the back button press and use it to launch a new MainActivity and clear the
* back stack. */
val intent = Intent(this@PreferencesActivity, MainActivity::class.java)
val intent = MainActivityIntent(this@PreferencesActivity)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivityWithSlideInAnimation(intent)
}
@ -73,15 +77,15 @@ class PreferencesActivity :
setDisplayShowHomeEnabled(true)
}
val preferenceType = intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)
val preferenceType = PreferencesActivityIntent.getPreferenceType(intent)
val fragmentTag = "preference_fragment_$preferenceType"
val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag)
?: when (preferenceType) {
GENERAL_PREFERENCES -> PreferencesFragment.newInstance()
ACCOUNT_PREFERENCES -> AccountPreferencesFragment.newInstance()
NOTIFICATION_PREFERENCES -> NotificationPreferencesFragment.newInstance()
PreferenceScreen.GENERAL -> PreferencesFragment.newInstance()
PreferenceScreen.ACCOUNT -> AccountPreferencesFragment.newInstance()
PreferenceScreen.NOTIFICATION -> NotificationPreferencesFragment.newInstance()
else -> throw IllegalArgumentException("preferenceType not known")
}
@ -164,17 +168,6 @@ class PreferencesActivity :
}
companion object {
const val GENERAL_PREFERENCES = 0
const val ACCOUNT_PREFERENCES = 1
const val NOTIFICATION_PREFERENCES = 2
private const val EXTRA_PREFERENCE_TYPE = "EXTRA_PREFERENCE_TYPE"
private const val EXTRA_RESTART_ON_BACK = "restart"
@JvmStatic
fun newIntent(context: Context, preferenceType: Int): Intent {
val intent = Intent(context, PreferencesActivity::class.java)
intent.putExtra(EXTRA_PREFERENCE_TYPE, preferenceType)
return intent
}
}
}

View File

@ -16,17 +16,19 @@
package app.pachli.components.report
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.components.report.adapter.ReportPagerAdapter
import app.pachli.core.navigation.ReportActivityIntent
import app.pachli.databinding.ActivityReportBinding
import app.pachli.util.viewBinding
import dagger.hilt.android.AndroidEntryPoint
/**
* Report a status or user.
*/
@AndroidEntryPoint
class ReportActivity : BottomSheetActivity() {
private val viewModel: ReportViewModel by viewModels()
@ -35,13 +37,13 @@ class ReportActivity : BottomSheetActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val accountId = intent?.getStringExtra(ACCOUNT_ID)
val accountUserName = intent?.getStringExtra(ACCOUNT_USERNAME)
if (accountId.isNullOrBlank() || accountUserName.isNullOrBlank()) {
throw IllegalStateException("accountId ($accountId) or accountUserName ($accountUserName) is null")
val accountId = ReportActivityIntent.getAccountId(intent)
val accountUserName = ReportActivityIntent.getAccountUserName(intent)
if (accountId.isBlank() || accountUserName.isBlank()) {
throw IllegalStateException("accountId ($accountId) or accountUserName ($accountUserName) is blank")
}
viewModel.init(accountId, accountUserName, intent?.getStringExtra(STATUS_ID))
viewModel.init(accountId, accountUserName, ReportActivityIntent.getStatusId(intent))
setContentView(binding.root)
@ -115,19 +117,4 @@ class ReportActivity : BottomSheetActivity() {
private fun showStatusesPage() {
binding.wizard.currentItem = 0
}
companion object {
private const val ACCOUNT_ID = "account_id"
private const val ACCOUNT_USERNAME = "account_username"
private const val STATUS_ID = "status_id"
@JvmStatic
fun getIntent(context: Context, accountId: String, userName: String, statusId: String? = null) =
Intent(context, ReportActivity::class.java)
.apply {
putExtra(ACCOUNT_ID, accountId)
putExtra(ACCOUNT_USERNAME, userName)
putExtra(STATUS_ID, statusId)
}
}
}

View File

@ -33,20 +33,20 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.ViewMediaActivity
import app.pachli.components.account.AccountActivity
import app.pachli.components.report.ReportViewModel
import app.pachli.components.report.Screen
import app.pachli.components.report.adapter.AdapterHandler
import app.pachli.components.report.adapter.StatusesAdapter
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Status
import app.pachli.databinding.FragmentReportStatusesBinding
import app.pachli.util.viewBinding
import app.pachli.util.visible
import app.pachli.viewdata.AttachmentViewData
import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.snackbar.Snackbar
@ -82,7 +82,7 @@ class ReportStatusesFragment :
when (actionable.attachments[idx].type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val attachments = AttachmentViewData.list(actionable)
val intent = ViewMediaActivity.newIntent(context, attachments, idx)
val intent = ViewMediaActivityIntent(requireContext(), attachments, idx)
if (v != null) {
val url = actionable.attachments[idx].url
ViewCompat.setTransitionName(v, url)
@ -209,9 +209,9 @@ class ReportStatusesFragment :
return viewModel.isStatusChecked(id)
}
override fun onViewAccount(id: String) = startActivity(AccountActivity.getIntent(requireContext(), id))
override fun onViewAccount(id: String) = startActivity(AccountActivityIntent(requireContext(), id))
override fun onViewTag(tag: String) = startActivity(StatusListActivity.newHashtagIntent(requireContext(), tag))
override fun onViewTag(tag: String) = startActivity(StatusListActivityIntent.hashtag(requireContext(), tag))
override fun onViewUrl(url: String) = viewModel.checkClickedUrl(url)

View File

@ -16,8 +16,6 @@
package app.pachli.components.scheduled
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -32,7 +30,8 @@ import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.appstore.EventHub
import app.pachli.appstore.StatusScheduledEvent
import app.pachli.components.compose.ComposeActivity
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.network.model.ScheduledStatus
import app.pachli.databinding.ActivityScheduledStatusBinding
import app.pachli.util.hide
@ -151,9 +150,9 @@ class ScheduledStatusActivity :
}
override fun edit(item: ScheduledStatus) {
val intent = ComposeActivity.startIntent(
val intent = ComposeActivityIntent(
this,
ComposeActivity.ComposeOptions(
ComposeOptions(
scheduledTootId = item.id,
content = item.params.text,
contentWarning = item.params.spoilerText,
@ -162,7 +161,7 @@ class ScheduledStatusActivity :
visibility = item.params.visibility,
scheduledAt = item.scheduledAt,
sensitive = item.params.sensitive,
kind = ComposeActivity.ComposeKind.EDIT_SCHEDULED,
kind = ComposeOptions.ComposeKind.EDIT_SCHEDULED,
),
)
startActivity(intent)
@ -177,8 +176,4 @@ class ScheduledStatusActivity :
}
.show()
}
companion object {
fun newIntent(context: Context) = Intent(context, ScheduledStatusActivity::class.java)
}
}

View File

@ -153,8 +153,4 @@ class SearchActivity : BottomSheetActivity(), MenuProvider, SearchView.OnQueryTe
return false
}
companion object {
fun getIntent(context: Context) = Intent(context, SearchActivity::class.java)
}
}

View File

@ -18,9 +18,9 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.account.AccountActivity
import app.pachli.components.search.SearchViewModel
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.databinding.FragmentSearchBinding
import app.pachli.interfaces.LinkListener
@ -141,11 +141,11 @@ abstract class SearchFragment<T : Any> :
}
override fun onViewAccount(id: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id))
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivityIntent(requireContext(), id))
}
override fun onViewTag(tag: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivityIntent.hashtag(requireContext(), tag))
}
override fun onViewUrl(url: String) {

View File

@ -38,12 +38,13 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.ViewMediaActivity
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.compose.ComposeActivity.ComposeOptions
import app.pachli.components.report.ReportActivity
import app.pachli.components.search.adapter.SearchStatusesAdapter
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.ReportActivityIntent
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.Status.Mention
@ -52,7 +53,6 @@ import app.pachli.interfaces.StatusActionListener
import app.pachli.util.StatusDisplayOptionsRepository
import app.pachli.util.openLink
import app.pachli.view.showMuteAccountDialog
import app.pachli.viewdata.AttachmentViewData
import app.pachli.viewdata.StatusViewData
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.divider.MaterialDividerItemDecoration
@ -119,8 +119,8 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
when (actionable.attachments[attachmentIndex].type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val attachments = AttachmentViewData.list(actionable)
val intent = ViewMediaActivity.newIntent(
context,
val intent = ViewMediaActivityIntent(
requireContext(),
attachments,
attachmentIndex,
)
@ -202,7 +202,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
remove(viewModel.activeAccount?.username)
}
val intent = ComposeActivity.startIntent(
val intent = ComposeActivityIntent(
requireContext(),
ComposeOptions(
inReplyToId = status.actionableId,
@ -212,7 +212,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
replyingStatusAuthor = actionableStatus.account.localUsername,
replyingStatusContent = status.content.toString(),
language = actionableStatus.language,
kind = ComposeActivity.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.NEW,
),
)
bottomSheetActivity?.startActivityWithSlideInAnimation(intent)
@ -423,7 +423,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
}
private fun openReportPage(accountId: String, accountUsername: String, statusId: String) {
startActivity(ReportActivity.getIntent(requireContext(), accountId, accountUsername, statusId))
startActivity(ReportActivityIntent(requireContext(), accountId, accountUsername, statusId))
}
private fun showConfirmDeleteDialog(id: String, position: Int) {
@ -455,7 +455,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
deletedStatus
}
val intent = ComposeActivity.startIntent(
val intent = ComposeActivityIntent(
requireContext(),
ComposeOptions(
content = redraftStatus.text.orEmpty(),
@ -466,7 +466,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
sensitive = redraftStatus.sensitive,
poll = redraftStatus.poll?.toNewPoll(status.createdAt),
language = redraftStatus.language,
kind = ComposeActivity.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.NEW,
),
)
startActivity(intent)
@ -497,9 +497,9 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
language = status.language,
statusId = source.id,
poll = status.poll?.toNewPoll(status.createdAt),
kind = ComposeActivity.ComposeKind.EDIT_POSTED,
kind = ComposeOptions.ComposeKind.EDIT_POSTED,
)
startActivity(ComposeActivity.startIntent(requireContext(), composeOptions))
startActivity(ComposeActivityIntent(requireContext(), composeOptions))
},
{
Snackbar.make(

View File

@ -42,8 +42,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.adapter.StatusBaseViewHolder
import app.pachli.components.accountlist.AccountListActivity
import app.pachli.components.accountlist.AccountListActivity.Companion.newIntent
import app.pachli.components.timeline.viewmodel.CachedTimelineViewModel
import app.pachli.components.timeline.viewmodel.InfallibleUiAction
import app.pachli.components.timeline.viewmodel.NetworkTimelineViewModel
@ -52,6 +50,8 @@ import app.pachli.components.timeline.viewmodel.StatusActionSuccess
import app.pachli.components.timeline.viewmodel.TimelineViewModel
import app.pachli.components.timeline.viewmodel.UiSuccess
import app.pachli.core.database.model.TranslationState
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import app.pachli.databinding.FragmentTimelineBinding
@ -72,7 +72,6 @@ import app.pachli.util.show
import app.pachli.util.viewBinding
import app.pachli.util.visible
import app.pachli.util.withPresentationState
import app.pachli.viewdata.AttachmentViewData
import app.pachli.viewdata.StatusViewData
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors
@ -635,13 +634,13 @@ class TimelineFragment :
override fun onShowReblogs(position: Int) {
val statusId = adapter.peek(position)?.id ?: return
val intent = newIntent(requireContext(), AccountListActivity.Type.REBLOGGED, statusId)
val intent = AccountListActivityIntent(requireContext(), AccountListActivityIntent.Kind.REBLOGGED, statusId)
(activity as BaseActivity).startActivityWithSlideInAnimation(intent)
}
override fun onShowFavs(position: Int) {
val statusId = adapter.peek(position)?.id ?: return
val intent = newIntent(requireContext(), AccountListActivity.Type.FAVOURITED, statusId)
val intent = AccountListActivityIntent(requireContext(), AccountListActivityIntent.Kind.FAVOURITED, statusId)
(activity as BaseActivity).startActivityWithSlideInAnimation(intent)
}

View File

@ -18,8 +18,6 @@
package app.pachli.components.trending
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
@ -86,10 +84,6 @@ class TrendingActivity : BottomSheetActivity(), AppBarLayoutHost, MenuProvider {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return super.onOptionsItemSelected(menuItem)
}
companion object {
fun getIntent(context: Context) = Intent(context, TrendingActivity::class.java)
}
}
class TrendingFragmentAdapter(val activity: FragmentActivity) : FragmentStateAdapter(activity) {

View File

@ -38,8 +38,8 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.trending.viewmodel.TrendingTagsViewModel
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.databinding.FragmentTrendingTagsBinding
import app.pachli.interfaces.ActionButtonActivity
import app.pachli.interfaces.AppBarLayoutHost
@ -173,7 +173,7 @@ class TrendingTagsFragment :
fun onViewTag(tag: String) {
(requireActivity() as BaseActivity).startActivityWithSlideInAnimation(
StatusListActivity.newHashtagIntent(
StatusListActivityIntent.hashtag(
requireContext(),
tag,
),

View File

@ -16,16 +16,18 @@
package app.pachli.components.viewthread
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.commit
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.core.navigation.ViewThreadActivityIntent
import app.pachli.databinding.ActivityViewThreadBinding
import app.pachli.util.viewBinding
import dagger.hilt.android.AndroidEntryPoint
/**
* View the statuses in a single thread.
*/
@AndroidEntryPoint
class ViewThreadActivity : BottomSheetActivity() {
private val binding by viewBinding(ActivityViewThreadBinding::inflate)
@ -39,8 +41,8 @@ class ViewThreadActivity : BottomSheetActivity() {
setDisplayShowHomeEnabled(true)
setDisplayShowTitleEnabled(true)
}
val id = intent.getStringExtra(ID_EXTRA)!!
val url = intent.getStringExtra(URL_EXTRA)!!
val id = ViewThreadActivityIntent.getStatusId(intent)
val url = ViewThreadActivityIntent.getUrl(intent)
val fragment =
supportFragmentManager.findFragmentByTag(FRAGMENT_TAG + id) as ViewThreadFragment?
?: ViewThreadFragment.newInstance(id, url)
@ -51,15 +53,6 @@ class ViewThreadActivity : BottomSheetActivity() {
}
companion object {
fun startIntent(context: Context, id: String, url: String): Intent {
val intent = Intent(context, ViewThreadActivity::class.java)
intent.putExtra(ID_EXTRA, id)
intent.putExtra(URL_EXTRA, url)
return intent
}
private const val ID_EXTRA = "id"
private const val URL_EXTRA = "url"
private const val FRAGMENT_TAG = "ViewThreadFragment_"
}
}

View File

@ -34,9 +34,9 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BaseActivity
import app.pachli.R
import app.pachli.components.accountlist.AccountListActivity
import app.pachli.components.accountlist.AccountListActivity.Companion.newIntent
import app.pachli.components.viewthread.edits.ViewEditsFragment
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.navigation.AttachmentViewData.Companion.list
import app.pachli.databinding.FragmentViewThreadBinding
import app.pachli.fragment.SFragment
import app.pachli.interfaces.StatusActionListener
@ -45,7 +45,6 @@ import app.pachli.util.hide
import app.pachli.util.openLink
import app.pachli.util.show
import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData.Companion.list
import app.pachli.viewdata.StatusViewData
import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration
@ -366,13 +365,13 @@ class ViewThreadFragment :
override fun onShowReblogs(position: Int) {
val statusId = adapter.currentList[position].id
val intent = newIntent(requireContext(), AccountListActivity.Type.REBLOGGED, statusId)
val intent = AccountListActivityIntent(requireContext(), AccountListActivityIntent.Kind.REBLOGGED, statusId)
(requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent)
}
override fun onShowFavs(position: Int) {
val statusId = adapter.currentList[position].id
val intent = newIntent(requireContext(), AccountListActivity.Type.FAVOURITED, statusId)
val intent = AccountListActivityIntent(requireContext(), AccountListActivityIntent.Kind.FAVOURITED, statusId)
(requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent)
}
@ -423,7 +422,7 @@ class ViewThreadFragment :
private const val ID_EXTRA = "id"
private const val URL_EXTRA = "url"
fun newInstance(id: String, url: String): ViewThreadFragment {
fun newInstance(id: String, url: String?): ViewThreadFragment {
val arguments = Bundle(2)
val fragment = ViewThreadFragment()
arguments.putString(ID_EXTRA, id)

View File

@ -31,9 +31,9 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BottomSheetActivity
import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.account.AccountActivity
import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.navigation.AccountActivityIntent
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository
import app.pachli.databinding.FragmentViewEditsBinding
@ -184,11 +184,11 @@ class ViewEditsFragment :
}
override fun onViewAccount(id: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id))
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivityIntent(requireContext(), id))
}
override fun onViewTag(tag: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivityIntent.hashtag(requireContext(), tag))
}
override fun onViewUrl(url: String) {

View File

@ -23,9 +23,9 @@ import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import app.pachli.R
import app.pachli.components.drafts.DraftsActivity
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.dao.DraftDao
import app.pachli.core.navigation.DraftsActivityIntent
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -66,7 +66,7 @@ class DraftsAlert @Inject constructor(private val draftDao: DraftDao) {
.setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int ->
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts
val intent = DraftsActivity.newIntent(context)
val intent = DraftsActivityIntent(context)
context.startActivity(intent)
}
.setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int ->

View File

@ -42,15 +42,15 @@ import app.pachli.BaseActivity
import app.pachli.BottomSheetActivity
import app.pachli.PostLookupFallbackBehavior
import app.pachli.R
import app.pachli.StatusListActivity.Companion.newHashtagIntent
import app.pachli.ViewMediaActivity.Companion.newIntent
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.compose.ComposeActivity.Companion.startIntent
import app.pachli.components.compose.ComposeActivity.ComposeOptions
import app.pachli.components.report.ReportActivity.Companion.getIntent
import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TranslationState
import app.pachli.core.navigation.AttachmentViewData
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.navigation.ReportActivityIntent
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.navigation.ViewMediaActivityIntent
import app.pachli.core.network.ServerOperation
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Status
@ -61,7 +61,6 @@ import app.pachli.network.ServerCapabilitiesRepository
import app.pachli.usecase.TimelineCases
import app.pachli.util.openLink
import app.pachli.view.showMuteAccountDialog
import app.pachli.viewdata.AttachmentViewData
import app.pachli.viewdata.StatusViewData
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
@ -163,10 +162,10 @@ abstract class SFragment : Fragment() {
replyingStatusAuthor = account.localUsername,
replyingStatusContent = actionableStatus.content.parseAsMastodonHtml().toString(),
language = actionableStatus.language,
kind = ComposeActivity.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.NEW,
)
val intent = startIntent(requireContext(), composeOptions)
val intent = ComposeActivityIntent(requireContext(), composeOptions)
requireActivity().startActivity(intent)
}
@ -379,7 +378,7 @@ abstract class SFragment : Fragment() {
val (attachment) = attachments[urlIndex]
when (attachment.type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val intent = newIntent(context, attachments, urlIndex)
val intent = ViewMediaActivityIntent(requireContext(), attachments, urlIndex)
if (view != null) {
val url = attachment.url
view.transitionName = url
@ -400,11 +399,11 @@ abstract class SFragment : Fragment() {
}
protected fun viewTag(tag: String) {
startActivity(newHashtagIntent(requireContext(), tag))
startActivity(StatusListActivityIntent.hashtag(requireContext(), tag))
}
private fun openReportPage(accountId: String, accountUsername: String, statusId: String) {
startActivity(getIntent(requireContext(), accountId, accountUsername, statusId))
startActivity(ReportActivityIntent(requireContext(), accountId, accountUsername, statusId))
}
private fun showConfirmDeleteDialog(id: String, position: Int) {
@ -455,9 +454,9 @@ abstract class SFragment : Fragment() {
modifiedInitialState = true,
language = sourceStatus.language,
poll = sourceStatus.poll?.toNewPoll(sourceStatus.createdAt),
kind = ComposeActivity.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.NEW,
)
startActivity(startIntent(requireContext(), composeOptions))
startActivity(ComposeActivityIntent(requireContext(), composeOptions))
},
{ error: Throwable? ->
Timber.w(error, "error deleting status")
@ -485,9 +484,9 @@ abstract class SFragment : Fragment() {
language = status.language,
statusId = source.id,
poll = status.poll?.toNewPoll(status.createdAt),
kind = ComposeActivity.ComposeKind.EDIT_POSTED,
kind = ComposeOptions.ComposeKind.EDIT_POSTED,
)
startActivity(startIntent(requireContext(), composeOptions))
startActivity(ComposeActivityIntent(requireContext(), composeOptions))
},
{
Snackbar.make(

View File

@ -24,8 +24,8 @@ import android.content.Intent
import android.os.Build
import android.service.quicksettings.TileService
import app.pachli.MainActivity
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.notifications.pendingIntentFlags
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
/**
* Small Addition that adds in a QuickSettings tile
@ -35,7 +35,7 @@ import app.pachli.components.notifications.pendingIntentFlags
class PachliTileService : TileService() {
@SuppressLint("StartActivityAndCollapseDeprecated")
override fun onClick() {
val intent = MainActivity.composeIntent(this, ComposeActivity.ComposeOptions())
val intent = MainActivity.composeIntent(this, ComposeOptions())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(getActivityPendingIntent(this, 0, intent))

View File

@ -1,5 +1,6 @@
package app.pachli.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@ -373,6 +374,8 @@ class SendStatusService : Service() {
}
private fun cancelSendingIntent(statusId: Int): PendingIntent {
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val intent = Intent(this, SendStatusService::class.java)
intent.putExtra(KEY_CANCEL, statusId)
return PendingIntent.getService(
@ -428,6 +431,8 @@ class SendStatusService : Service() {
context: Context,
statusToSend: StatusToSend,
): Intent {
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val intent = Intent(context, SendStatusService::class.java)
intent.putExtra(KEY_STATUS, statusToSend)

View File

@ -25,9 +25,9 @@ import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import app.pachli.MainActivity
import app.pachli.R
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.MainActivityIntent
import com.bumptech.glide.Glide
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
@ -67,7 +67,7 @@ fun updateShortcut(context: Context, account: AccountEntity) {
.build()
// This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different
val intent = Intent(context, MainActivity::class.java).apply {
val intent = MainActivityIntent(context).apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString())

View File

@ -33,6 +33,7 @@ import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TabKind
import app.pachli.core.database.model.defaultTabs
import app.pachli.core.navigation.AccountListActivityIntent
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.TimelineAccount
@ -176,7 +177,7 @@ class MainActivityTest {
nextActivity.component,
)
assertEquals(
AccountListActivity.Type.FOLLOW_REQUESTS,
AccountListActivityIntent.Kind.FOLLOW_REQUESTS,
nextActivity.getSerializableExtra("type"),
)
}

View File

@ -24,6 +24,8 @@ import app.pachli.PachliApplication
import app.pachli.R
import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.core.accounts.AccountManager
import app.pachli.core.navigation.ComposeActivityIntent
import app.pachli.core.navigation.ComposeActivityIntent.ComposeOptions
import app.pachli.core.network.model.Account
import app.pachli.core.network.model.InstanceConfiguration
import app.pachli.core.network.model.InstanceV1
@ -138,7 +140,7 @@ class ComposeActivityTest {
@Test
fun whenModifiedInitialState_andCloseButtonPressed_notFinish() {
rule.launch(intent(ComposeActivity.ComposeOptions(modifiedInitialState = true)))
rule.launch(intent(ComposeOptions(modifiedInitialState = true)))
rule.getScenario().onActivity {
clickUp(it)
assertFalse(it.isFinishing)
@ -167,7 +169,7 @@ class ComposeActivityTest {
@Test
fun whenModifiedInitialState_andBackButtonPressed_notFinish() {
rule.launch(intent(ComposeActivity.ComposeOptions(modifiedInitialState = true)))
rule.launch(intent(ComposeOptions(modifiedInitialState = true)))
rule.getScenario().onActivity {
clickBack(it)
assertFalse(it.isFinishing)
@ -512,7 +514,7 @@ class ComposeActivityTest {
@Test
fun languageGivenInComposeOptionsIsRespected() {
rule.launch(intent(ComposeActivity.ComposeOptions(language = "no")))
rule.launch(intent(ComposeOptions(language = "no")))
rule.getScenario().onActivity {
assertEquals("no", it.selectedLanguage)
}
@ -522,7 +524,7 @@ class ComposeActivityTest {
fun modernLanguageCodeIsUsed() {
// https://github.com/tuskyapp/Tusky/issues/2903
// "ji" was deprecated in favor of "yi"
rule.launch(intent(ComposeActivity.ComposeOptions(language = "ji")))
rule.launch(intent(ComposeOptions(language = "ji")))
rule.getScenario().onActivity {
assertEquals("yi", it.selectedLanguage)
}
@ -530,14 +532,14 @@ class ComposeActivityTest {
@Test
fun unknownLanguageGivenInComposeOptionsIsRespected() {
rule.launch(intent(ComposeActivity.ComposeOptions(language = "zzz")))
rule.launch(intent(ComposeOptions(language = "zzz")))
rule.getScenario().onActivity {
assertEquals("zzz", it.selectedLanguage)
}
}
/** Returns an intent to launch [ComposeActivity] with the given options */
private fun intent(composeOptions: ComposeActivity.ComposeOptions) = ComposeActivity.startIntent(
private fun intent(composeOptions: ComposeOptions) = ComposeActivityIntent(
ApplicationProvider.getApplicationContext(),
composeOptions,
)

View File

@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.aboutlibraries) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.quadrant) apply false
}
allprojects {

View File

@ -0,0 +1,80 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.lint.checks
import com.android.SdkConstants.CLASS_INTENT
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getQualifiedName
import org.jetbrains.uast.util.isConstructorCall
class IntentDetector : Detector(), Detector.UastScanner {
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
// Ignore anything that is not constructing an Intent
if (!node.isConstructorCall()) return
val classRef = node.classReference ?: return
val className = classRef.getQualifiedName()
if (className != CLASS_INTENT) return
// Ignore calls that don't have 2 or 4 parameters
val constructor = node.resolve() ?: return
val parameters = constructor.parameterList.parameters
if (parameters.size != 2 && parameters.size != 4) return
// Ignore calls where the last parameter is not a class literal
val lastParam = parameters.last()
if (lastParam.type.canonicalText != "java.lang.Class<?>") return
context.report(
issue = ISSUE,
scope = node,
location = context.getCallLocation(node, true, true),
message = "Use functions from `core.navigation`",
)
}
}
companion object {
val ISSUE = Issue.create(
id = "IntentDetector",
briefDescription = "Don't use `Intent(...)`, use functions from core.navigation",
explanation = """
Creating an `Intent` with a class from another module can create unnecessary or circular
dependencies. Use the `...Intent` classes in `core.navigation` to create an intent for
the appropriate `Activity`.
""".trimIndent(),
category = Category.CORRECTNESS,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
IntentDetector::class.java,
Scope.JAVA_FILE_SCOPE,
),
)
}
}

View File

@ -7,7 +7,10 @@ import com.android.tools.lint.detector.api.Issue
@Suppress("UnstableApiUsage")
class LintRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(AndroidxToolbarDetector.ISSUE)
get() = listOf(
AndroidxToolbarDetector.ISSUE,
IntentDetector.ISSUE,
)
override val api: Int
get() = CURRENT_API

View File

@ -0,0 +1,171 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.lint.checks
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
class IntentDetectorTest : LintDetectorTest() {
override fun getDetector(): Detector = IntentDetector()
override fun getIssues(): List<Issue> = listOf(IntentDetector.ISSUE)
fun `test Intent component constructor emits warning`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Context
import android.content.Intent
fun makeIntent(context: Context) = Intent(context, String::class.java)
""",
).indented(),
).allowMissingSdk().run().expect(
"""src/test/pkg/test.kt:6: Warning: Use functions from core.navigation [IntentDetector]
fun makeIntent(context: Context) = Intent(context, String::class.java)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings""",
)
}
fun `test Intent action and data component constructor emits warning`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Context
import android.content.Intent
fun makeIntent(context: Context) = Intent(
"someAction",
Uri.parse("https://example.com"),
context,
String::class.java,
)
""",
).indented(),
).allowMissingSdk().run().expect(
"""src/test/pkg/test.kt:6: Warning: Use functions from core.navigation [IntentDetector]
fun makeIntent(context: Context) = Intent(
^
0 errors, 1 warnings""",
)
}
fun `test empty constructor does not warn`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Intent
fun makeIntent() = Intent()
""",
).indented(),
).allowMissingSdk().run().expectClean()
}
fun `test copy constructor does not warn`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Intent
fun makeIntent(intent: Intent) = Intent(intent, 0)
""",
).indented(),
).allowMissingSdk().run().expectClean()
}
fun `test action constructor does not warn`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Intent
fun makeIntent() = Intent("some action")
""",
).indented(),
).allowMissingSdk().run().expectClean()
}
fun `test action and uri constructor does not warn`() {
lint().files(
Context,
Intent,
kotlin(
"""
package test.pkg
import android.content.Intent
fun makeIntent() = Intent("some action", Uri.parse("http://example.com"))
""",
).indented(),
).allowMissingSdk().run().expectClean()
}
companion object Stubs {
/** Stub for `android.content.Context` */
private val Context = java(
"""
package android.content;
public class Context {}
""",
).indented()
/** Stub for `android.content.Intent` */
private val Intent = java(
"""
package android.content;
import android.content.Context;
public class Intent {
public Intent() { return null; }
public Intent(Intent o, int copyMode) { return null; }
public Intent(String action) { return null; }
public Intent(String action, Uri uri) { return null; }
public Intent(Context packageContext, Class<?> cls) { return null; }
public Intent(String action, Uri uri, Context packageContext, Class<?> cls) { return null; }
}
""",
).indented()
}
}

View File

@ -36,5 +36,5 @@ dependencies {
implementation(projects.core.preferences)
// Because of the use of @SerializedName in DraftEntity
compileOnly(libs.gson)
implementation(libs.gson)
}

23
core/navigation/README.md Normal file
View File

@ -0,0 +1,23 @@
# :core:navigation
## package app.pachli.core.navigation
Intents for starting activities to break circular dependencies.
A common approach for surfacing type-safe (ish) intents to start activities is for the activity-to-be-launched to provide a method in a companion object that returns the relevant intent, possibly taking additional parameters that will be included in the intent as extras.
E.g., if A wants to start B, B provides the method that returns the intent.
This introduces a dependency between A and B.
This is worse if B also wants to start A.
For example, if A is `StatusListActivity` and B is`ViewThreadActivity`. The user might click a status in `StatusListActivity` to view the thread, starting `ViewThreadActivity`. But from the thread they might click a hashtag to view the list of statuses with that hashtag. Now `StatusListActivity` and `ViewThreadActivity` have a circular dependency.
Even if that doesn't happen the dependency means that any changes to B will trigger a rebuild of A, even if the changes to B are not relevant.
This package contains `Intent` subclasses that should be used instead. The `quadrant` plugin is used to generate constants that can be used to launch activities by name instead of by class, breaking the dependency chain.
If the activity's intent requires specific extras those are passed via the constructor, with companion object methods to extract them from the intent.
Using the intent classes from this package is enforced by a lint `IntentDetector` which will warn if any intents are created using a class literal.

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
plugins {
alias(libs.plugins.pachli.android.library)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.quadrant)
}
android {
namespace = "app.pachli.core.navigation"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
implementation(projects.core.database) // For DraftAttachment, used in ComposeOptions
implementation(projects.core.network) // For Attachment, used in AttachmentViewData
implementation(libs.androidx.core.ktx) // IntentCompat
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.2.0" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0)" variant="all" version="8.2.0">
</issues>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2023 Pachli Association
~
~ This file is a part of Pachli.
~
~ This program is free software; you can redistribute it and/or modify it under the terms of the
~ GNU General Public License as published by the Free Software Foundation; either version 3 of the
~ License, or (at your option) any later version.
~
~ Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
~ Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
~ see <http://www.gnu.org/licenses>.
-->
<manifest>
</manifest>

View File

@ -1,4 +1,5 @@
/* Copyright 2022 Tusky Contributors
/*
* Copyright 2022 Tusky Contributors
*
* This file is a part of Pachli.
*
@ -14,7 +15,7 @@
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.viewdata
package app.pachli.core.navigation
import android.os.Parcelable
import app.pachli.core.network.model.Attachment

View File

@ -0,0 +1,482 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.core.navigation
import android.content.Context
import android.content.Intent
import android.os.Parcelable
import androidx.core.content.IntentCompat
import app.pachli.core.database.model.DraftAttachment
import app.pachli.core.navigation.LoginActivityIntent.LoginMode
import app.pachli.core.navigation.StatusListActivityIntent.Companion.bookmarks
import app.pachli.core.navigation.StatusListActivityIntent.Companion.favourites
import app.pachli.core.navigation.StatusListActivityIntent.Companion.hashtag
import app.pachli.core.navigation.StatusListActivityIntent.Companion.list
import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.NewPoll
import app.pachli.core.network.model.Status
import app.pachli.core.network.model.TimelineKind
import com.gaelmarhic.quadrant.QuadrantConstants
import kotlinx.parcelize.Parcelize
/**
* @param context
* @param accountId Server ID of the account to view
* @see [app.pachli.components.account.AccountActivity]
*/
class AccountActivityIntent(context: Context, accountId: String) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.ACCOUNT_ACTIVITY}")
putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
}
companion object {
private const val EXTRA_KEY_ACCOUNT_ID = "id"
/** @return the account ID passed in this intent */
fun getAccountId(intent: Intent) = intent.getStringExtra(EXTRA_KEY_ACCOUNT_ID)!!
}
}
/**
* @param context
* @param kind The kind of accounts to show
* @param id Optional ID. Sometimes an account ID, sometimes a status ID, and
* sometimes ignored. See [Kind] for details of how `id` is interpreted.
* @see [app.pachli.components.accountlist.AccountListActivity]
*/
class AccountListActivityIntent(context: Context, kind: Kind, id: String? = null) : Intent() {
enum class Kind {
/** Show the accounts the account with `id` is following */
FOLLOWS,
/** Show the accounts following the account with `id` */
FOLLOWERS,
/** Show the accounts the account with `id` is blocking */
BLOCKS,
/** Show the accounts the account with `id` is muting */
MUTES,
/** Show the logged in account's follow requests (`id` is ignored) */
FOLLOW_REQUESTS,
/** Show the accounts that reblogged the status with `id` */
REBLOGGED,
/** Show the accounts that favourited the status with `id` */
FAVOURITED,
}
init {
setClassName(context, "app.pachli${QuadrantConstants.ACCOUNT_LIST_ACTIVITY}")
putExtra(EXTRA_KIND, kind)
putExtra(EXTRA_ID, id)
}
companion object {
private const val EXTRA_KIND = "kind"
private const val EXTRA_ID = "id"
/** @return The [Kind] passed in this intent */
fun getKind(intent: Intent) = intent.getSerializableExtra(EXTRA_KIND) as Kind
/** @return The ID passed in this intent, or null */
fun getId(intent: Intent) = intent.getStringExtra(EXTRA_ID)
}
}
/**
* @param context
* @see [app.pachli.components.compose.ComposeActivity]
*/
class ComposeActivityIntent(context: Context) : Intent() {
@Parcelize
data class ComposeOptions(
val scheduledTootId: String? = null,
val draftId: Int? = null,
val content: String? = null,
val mediaUrls: List<String>? = null,
val mediaDescriptions: List<String>? = null,
val mentionedUsernames: Set<String>? = null,
val inReplyToId: String? = null,
val replyVisibility: Status.Visibility? = null,
val visibility: Status.Visibility? = null,
val contentWarning: String? = null,
val replyingStatusAuthor: String? = null,
val replyingStatusContent: String? = null,
val mediaAttachments: List<Attachment>? = null,
val draftAttachments: List<DraftAttachment>? = null,
val scheduledAt: String? = null,
val sensitive: Boolean? = null,
val poll: NewPoll? = null,
val modifiedInitialState: Boolean? = null,
val language: String? = null,
val statusId: String? = null,
val kind: ComposeKind? = null,
val initialCursorPosition: InitialCursorPosition = InitialCursorPosition.END,
) : Parcelable {
/**
* Status' kind. This particularly affects how the status is handled if the user
* backs out of the edit.
*/
enum class ComposeKind {
/** Status is new */
NEW,
/** Editing a posted status */
EDIT_POSTED,
/** Editing a status started as an existing draft */
EDIT_DRAFT,
/** Editing an an existing scheduled status */
EDIT_SCHEDULED,
}
/**
* Initial position of the cursor in EditText when the compose button is clicked
* in a hashtag timeline
*/
enum class InitialCursorPosition {
/** Position the cursor at the start of the line */
START,
/** Position the cursor at the end of the line */
END,
}
}
init {
setClassName(context, "app.pachli${QuadrantConstants.COMPOSE_ACTIVITY}")
}
/**
* @param context
* @param options Configure the initial state of the activity
* @see [app.pachli.components.compose.ComposeActivity]
*/
constructor(context: Context, options: ComposeOptions) : this(context) {
putExtra(EXTRA_COMPOSE_OPTIONS, options)
}
companion object {
private const val EXTRA_COMPOSE_OPTIONS = "composeOptions"
/** @return the [ComposeOptions] passed in this intent, or null */
fun getOptions(intent: Intent) = IntentCompat.getParcelableExtra(intent, EXTRA_COMPOSE_OPTIONS, ComposeOptions::class.java)
}
}
/**
* @param context
* @param filter Optional filter to edit. If null an empty filter is created.
* @see [app.pachli.components.filters.EditFilterActivity]
*/
class EditFilterActivityIntent(context: Context, filter: Filter? = null) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.EDIT_FILTER_ACTIVITY}")
filter?.let {
putExtra(EXTRA_FILTER_TO_EDIT, it)
}
}
companion object {
const val EXTRA_FILTER_TO_EDIT = "filterToEdit"
/** @return the [Filter] passed in this intent, or null */
fun getFilter(intent: Intent) = IntentCompat.getParcelableExtra(intent, EXTRA_FILTER_TO_EDIT, Filter::class.java)
}
}
/**
* @param context
* @param loginMode See [LoginMode]
* @see [app.pachli.components.login.LoginActivity]
*/
class LoginActivityIntent(context: Context, loginMode: LoginMode = LoginMode.DEFAULT) : Intent() {
/** How to log in */
enum class LoginMode {
DEFAULT,
/** Already logged in, log in with an additional account */
ADDITIONAL_LOGIN,
/** Update the OAuth scope granted to the client */
MIGRATION,
}
init {
setClassName(context, "app.pachli${QuadrantConstants.LOGIN_ACTIVITY}")
putExtra(EXTRA_LOGIN_MODE, loginMode)
}
companion object {
private const val EXTRA_LOGIN_MODE = "loginMode"
/** @return the `loginMode` passed to this intent */
fun getLoginMode(intent: Intent) = intent.getSerializableExtra(EXTRA_LOGIN_MODE)!! as LoginMode
}
}
/**
* @param context
* @param screen The preference screen to show
* @see [app.pachli.components.preference.PreferencesActivity]
*/
class PreferencesActivityIntent(context: Context, screen: PreferenceScreen) : Intent() {
/** A specific preference screen */
enum class PreferenceScreen {
/** General preferences */
GENERAL,
/** Account-specific preferences */
ACCOUNT,
/** Notification preferences */
NOTIFICATION,
}
init {
setClassName(context, "app.pachli${QuadrantConstants.PREFERENCES_ACTIVITY}")
putExtra(EXTRA_PREFERENCE_SCREEN, screen)
}
companion object {
private const val EXTRA_PREFERENCE_SCREEN = "preferenceScreen"
/** @return the `screen` passed to this intent */
fun getPreferenceType(intent: Intent) = intent.getSerializableExtra(EXTRA_PREFERENCE_SCREEN)!! as PreferenceScreen
}
}
/**
* @param context
* @param accountId The ID of the account to report
* @param userName The username of the account to report
* @param statusId Optional ID of a status to include in the report
* @see [app.pachli.components.report.ReportActivity]
*/
class ReportActivityIntent(context: Context, accountId: String, userName: String, statusId: String? = null) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.REPORT_ACTIVITY}")
putExtra(EXTRA_ACCOUNT_ID, accountId)
putExtra(EXTRA_ACCOUNT_USERNAME, userName)
putExtra(EXTRA_STATUS_ID, statusId)
}
companion object {
private const val EXTRA_ACCOUNT_ID = "accountId"
private const val EXTRA_ACCOUNT_USERNAME = "accountUsername"
private const val EXTRA_STATUS_ID = "statusId"
/** @return the `accountId` passed to this intent */
fun getAccountId(intent: Intent) = intent.getStringExtra(EXTRA_ACCOUNT_ID)!!
/** @return the `userName` passed to this intent */
fun getAccountUserName(intent: Intent) = intent.getStringExtra(EXTRA_ACCOUNT_USERNAME)!!
/** @return the `statusId` passed to this intent, or null */
fun getStatusId(intent: Intent) = intent.getStringExtra(EXTRA_STATUS_ID)
}
}
/**
* Use one of [bookmarks], [favourites], [hashtag], or [list] to construct.
*/
class StatusListActivityIntent private constructor (context: Context) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.STATUS_LIST_ACTIVITY}")
}
companion object {
private const val EXTRA_KIND = "kind"
/**
* Show the user's bookmarks.
*
* @param context
*/
fun bookmarks(context: Context) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Bookmarks)
}
/**
* Show the user's favourites.
*
* @param context
*/
fun favourites(context: Context) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Favourites)
}
/**
* Show statuses containing [hashtag].
*
* @param context
* @param hashtag The hashtag to show, without the leading "`#`"
*/
fun hashtag(context: Context, hashtag: String) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.Tag(listOf(hashtag)))
}
/**
* Show statuses from a list.
*
* @param context
* @param listId ID of the list to show
* @param title The title to display
*/
fun list(context: Context, listId: String, title: String) = StatusListActivityIntent(context).apply {
putExtra(EXTRA_KIND, TimelineKind.UserList(listId, title))
}
/** @return The [TimelineKind] to show */
fun getKind(intent: Intent) = IntentCompat.getParcelableExtra(intent, EXTRA_KIND, TimelineKind::class.java)!!
}
}
class ViewMediaActivityIntent private constructor(context: Context) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.VIEW_MEDIA_ACTIVITY}")
}
/**
* Show a collection of media attachments.
*
* @param context
* @param attachments The attachments to show
* @param index The index of the attachment in [attachments] to focus on
*/
constructor(context: Context, attachments: List<AttachmentViewData>, index: Int) : this(context) {
putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
putExtra(EXTRA_ATTACHMENT_INDEX, index)
}
/**
* Show a single image identified by a URL
*
* @param context
* @param url The URL of the image
*/
constructor(context: Context, url: String) : this(context) {
putExtra(EXTRA_SINGLE_IMAGE_URL, url)
}
companion object {
private const val EXTRA_ATTACHMENTS = "attachments"
private const val EXTRA_ATTACHMENT_INDEX = "index"
private const val EXTRA_SINGLE_IMAGE_URL = "singleImage"
/** @return the list of [AttachmentViewData] passed in this intent, or null */
fun getAttachments(intent: Intent): ArrayList<AttachmentViewData>? = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_ATTACHMENTS, AttachmentViewData::class.java)
/** @return the index of the attachment to show, or 0 */
fun getAttachmentIndex(intent: Intent) = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
/** @return the URL of the single image to show, null if no URL was included */
fun getImageUrl(intent: Intent) = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
}
}
/**
* @param context
* @param statusId ID of the status to start from (may be in the middle of the thread)
* @param statusUrl Optional URL of the status in `statusId`
* @see [app.pachli.components.viewthread.ViewThreadActivity]
*/
class ViewThreadActivityIntent(context: Context, statusId: String, statusUrl: String? = null) : Intent() {
init {
setClassName(context, "app.pachli${QuadrantConstants.VIEW_THREAD_ACTIVITY}")
putExtra(EXTRA_STATUS_ID, statusId)
putExtra(EXTRA_STATUS_URL, statusUrl)
}
companion object {
private const val EXTRA_STATUS_ID = "id"
private const val EXTRA_STATUS_URL = "url"
/** @return the `statusId` passed to this intent */
fun getStatusId(intent: Intent) = intent.getStringExtra(EXTRA_STATUS_ID)!!
/** @return the `statusUrl` passed to this intent, or null */
fun getUrl(intent: Intent) = intent.getStringExtra(EXTRA_STATUS_URL)
}
}
class AboutActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.ABOUT_ACTIVITY}") }
}
class AnnouncementsActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.ANNOUNCEMENTS_ACTIVITY}") }
}
class DraftsActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.DRAFTS_ACTIVITY}") }
}
class EditProfileActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.EDIT_PROFILE_ACTIVITY}") }
}
class FiltersActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.FILTERS_ACTIVITY}") }
}
class FollowedTagsActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.FOLLOWED_TAGS_ACTIVITY}") }
}
class InstanceListActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.INSTANCE_LIST_ACTIVITY}") }
}
class LicenseActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.LICENSE_ACTIVITY}") }
}
class ListActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.LISTS_ACTIVITY}") }
}
class LoginWebViewActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.LOGIN_WEB_VIEW_ACTIVITY}") }
}
class MainActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.MAIN_ACTIVITY}") }
}
class PrivacyPolicyActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.PRIVACY_POLICY_ACTIVITY}") }
}
class ScheduledStatusActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.SCHEDULED_STATUS_ACTIVITY}") }
}
class SearchActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.SEARCH_ACTIVITY}") }
}
class TabPreferenceActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.TAB_PREFERENCE_ACTIVITY}") }
}
class TrendingActivityIntent(context: Context) : Intent() {
init { setClassName(context, "app.pachli${QuadrantConstants.TRENDING_ACTIVITY}") }
}

View File

@ -50,6 +50,7 @@ mockito-inline = "5.2.0"
mockito-kotlin = "5.2.1"
networkresult-calladapter = "1.0.0"
okhttp = "4.12.0"
quadrant = "1.7"
retrofit = "2.9.0"
robolectric = "4.11.1"
rxandroid3 = "3.0.2"
@ -76,6 +77,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
ktlint = "org.jlleitschuh.gradle.ktlint:11.6.1"
room = { id = "androidx.room", version.ref = "androidx-room" }
quadrant = { id = "com.gaelmarhic.quadrant", version.ref = "quadrant" }
# Plugins defined by this project
pachli-android-application = { id = "pachli.android.application", version = "unspecified" }

View File

@ -27,6 +27,7 @@ include(":core:accounts")
include(":core:common")
include(":core:database")
include(":core:preferences")
include(":core:navigation")
include(":core:network")
include(":core:testing")
include(":tools:mklanguages")