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.accounts)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.core.database) implementation(projects.core.database)
implementation(projects.core.navigation)
implementation(projects.core.network) implementation(projects.core.network)
implementation(projects.core.preferences) implementation(projects.core.preferences)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,8 +17,6 @@
package app.pachli package app.pachli
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -26,8 +24,10 @@ import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.pachli.appstore.EventHub import app.pachli.appstore.EventHub
import app.pachli.appstore.FilterChangedEvent import app.pachli.appstore.FilterChangedEvent
import app.pachli.components.compose.ComposeActivity
import app.pachli.components.timeline.TimelineFragment 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.Filter
import app.pachli.core.network.model.FilterV1 import app.pachli.core.network.model.FilterV1
import app.pachli.core.network.model.TimelineKind import app.pachli.core.network.model.TimelineKind
@ -84,7 +84,7 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
setSupportActionBar(binding.includedToolbar.toolbar) setSupportActionBar(binding.includedToolbar.toolbar)
timelineKind = intent.getParcelableExtra(EXTRA_KIND)!! timelineKind = StatusListActivityIntent.getKind(intent)
val title = when (timelineKind) { val title = when (timelineKind) {
is TimelineKind.Favourites -> getString(R.string.title_favourites) is TimelineKind.Favourites -> getString(R.string.title_favourites)
@ -113,11 +113,11 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
val composeIntent = when (timelineKind) { val composeIntent = when (timelineKind) {
is TimelineKind.Tag -> { is TimelineKind.Tag -> {
val tag = (timelineKind as TimelineKind.Tag).tags.first() val tag = (timelineKind as TimelineKind.Tag).tags.first()
ComposeActivity.startIntent( ComposeActivityIntent(
this, this,
ComposeActivity.ComposeOptions( ComposeOptions(
content = getString(R.string.title_tag_with_initial_position).format(tag), 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.Favourites,
is TimelineKind.UserList, is TimelineKind.UserList,
-> { -> {
ComposeActivity.startIntent( ComposeActivityIntent(this, ComposeOptions())
this,
ComposeActivity.ComposeOptions(),
)
} }
else -> null else -> null
} }
@ -367,29 +364,4 @@ class StatusListActivity : BottomSheetActivity(), AppBarLayoutHost, ActionButton
return true 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 package app.pachli
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.Gravity import android.view.Gravity
@ -44,6 +43,7 @@ import app.pachli.appstore.EventHub
import app.pachli.appstore.MainTabsChangedEvent import app.pachli.appstore.MainTabsChangedEvent
import app.pachli.core.database.model.TabData import app.pachli.core.database.model.TabData
import app.pachli.core.database.model.TabKind 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.model.MastoList
import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.databinding.ActivityTabPreferenceBinding import app.pachli.databinding.ActivityTabPreferenceBinding
@ -303,7 +303,7 @@ class TabPreferenceActivity : BaseActivity(), ItemInteractionListener {
val dialogBuilder = AlertDialog.Builder(this) val dialogBuilder = AlertDialog.Builder(this)
.setTitle(R.string.select_list_title) .setTitle(R.string.select_list_title)
.setNeutralButton(R.string.select_list_manage) { _, _ -> .setNeutralButton(R.string.select_list_manage) { _, _ ->
val listIntent = Intent(applicationContext, ListsActivity::class.java) val listIntent = ListActivityIntent(applicationContext)
startActivity(listIntent) startActivity(listIntent)
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)

View File

@ -24,7 +24,6 @@ import android.app.DownloadManager
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
@ -40,13 +39,14 @@ import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.core.app.ShareCompat import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.content.IntentCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import app.pachli.BuildConfig.APPLICATION_ID 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.core.network.model.Attachment
import app.pachli.databinding.ActivityViewMediaBinding import app.pachli.databinding.ActivityViewMediaBinding
import app.pachli.fragment.ViewImageFragment import app.pachli.fragment.ViewImageFragment
@ -55,7 +55,6 @@ import app.pachli.pager.ImagePagerAdapter
import app.pachli.pager.SingleImagePagerAdapter import app.pachli.pager.SingleImagePagerAdapter
import app.pachli.util.getTemporaryMediaFilename import app.pachli.util.getTemporaryMediaFilename
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider
import autodispose2.autoDispose import autodispose2.autoDispose
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -73,6 +72,9 @@ import java.util.Locale
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
/**
* Show one or more media items (pictures, video, audio, etc).
*/
@AndroidEntryPoint @AndroidEntryPoint
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener, ViewVideoFragment.VideoActionsListener { class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener, ViewVideoFragment.VideoActionsListener {
private val binding by viewBinding(ActivityViewMediaBinding::inflate) private val binding by viewBinding(ActivityViewMediaBinding::inflate)
@ -100,8 +102,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
supportPostponeEnterTransition() supportPostponeEnterTransition()
// Gather the parameters. // Gather the parameters.
attachments = IntentCompat.getParcelableArrayListExtra(intent, EXTRA_ATTACHMENTS, AttachmentViewData::class.java) attachments = ViewMediaActivityIntent.getAttachments(intent)
val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0) val initialPosition = ViewMediaActivityIntent.getAttachmentIndex(intent)
// Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
// but it cannot be expressed and if I don't specify type explicitly compilation fails // 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. // Setup the view pager.
ImagePagerAdapter(this, realAttachs, initialPosition) ImagePagerAdapter(this, realAttachs, initialPosition)
} else { } else {
imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL) imageUrl = ViewMediaActivityIntent.getImageUrl(intent)
?: throw IllegalArgumentException("attachment list or image url has to be set") ?: throw IllegalArgumentException("attachment list or image url has to be set")
SingleImagePagerAdapter(this, imageUrl!!) SingleImagePagerAdapter(this, imageUrl!!)
@ -241,7 +243,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
private fun onOpenStatus() { private fun onOpenStatus() {
val attach = attachments!![binding.viewPager.currentItem] val attach = attachments!![binding.viewPager.currentItem]
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl)) startActivityWithSlideInAnimation(ViewThreadActivityIntent(this, attach.statusId, attach.statusUrl))
} }
private fun copyLink() { private fun copyLink() {
@ -344,27 +346,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
shareFile(file, mimeType) 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) { 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.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import app.pachli.R import app.pachli.R
import app.pachli.ViewMediaActivity.Companion.newSingleImageIntent
import app.pachli.core.common.util.AbsoluteTimeFormatter import app.pachli.core.common.util.AbsoluteTimeFormatter
import app.pachli.core.common.util.formatNumber import app.pachli.core.common.util.formatNumber
import app.pachli.core.database.model.TranslationState 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.Attachment
import app.pachli.core.network.model.Emoji import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.PreviewCardKind 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 -> cardView.bind(card, status.actionable.sensitive, statusDisplayOptions) { target ->
if (card.kind == PreviewCardKind.PHOTO && card.embedUrl.isNotEmpty() && target == PreviewCardView.Target.IMAGE) { if (card.kind == PreviewCardKind.PHOTO && card.embedUrl.isNotEmpty() && target == PreviewCardView.Target.IMAGE) {
context.startActivity( context.startActivity(
newSingleImageIntent(context, card.embedUrl), ViewMediaActivityIntent(context, card.embedUrl),
) )
} else { } else {
listener.onViewUrl(card.url) listener.onViewUrl(card.url)

View File

@ -47,15 +47,17 @@ import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer import androidx.viewpager2.widget.MarginPageTransformer
import app.pachli.BottomSheetActivity import app.pachli.BottomSheetActivity
import app.pachli.EditProfileActivity
import app.pachli.R import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.ViewMediaActivity
import app.pachli.components.account.list.ListsForAccountFragment 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.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.Account
import app.pachli.core.network.model.Relationship import app.pachli.core.network.model.Relationship
import app.pachli.core.network.parseAsMastodonHtml import app.pachli.core.network.parseAsMastodonHtml
@ -100,6 +102,9 @@ import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
/**
* Show a single account's profile details.
*/
@AndroidEntryPoint @AndroidEntryPoint
class AccountActivity : class AccountActivity :
BottomSheetActivity(), BottomSheetActivity(),
@ -172,7 +177,7 @@ class AccountActivity :
addMenuProvider(this) addMenuProvider(this)
// Obtain information to fill out the profile. // 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) animateAvatar = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
animateEmojis = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) animateEmojis = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
@ -221,12 +226,12 @@ class AccountActivity :
binding.accountFieldList.adapter = accountFieldAdapter binding.accountFieldList.adapter = accountFieldAdapter
val accountListClickListener = { v: View -> val accountListClickListener = { v: View ->
val type = when (v.id) { val kind = when (v.id) {
R.id.accountFollowers -> AccountListActivity.Type.FOLLOWERS R.id.accountFollowers -> AccountListActivityIntent.Kind.FOLLOWERS
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWS R.id.accountFollowing -> AccountListActivityIntent.Kind.FOLLOWS
else -> throw AssertionError() else -> throw AssertionError()
} }
val accountListIntent = AccountListActivity.newIntent(this, type, viewModel.accountId) val accountListIntent = AccountListActivityIntent(this, kind, viewModel.accountId)
startActivityWithSlideInAnimation(accountListIntent) startActivityWithSlideInAnimation(accountListIntent)
} }
binding.accountFollowers.setOnClickListener(accountListClickListener) binding.accountFollowers.setOnClickListener(accountListClickListener)
@ -540,7 +545,7 @@ class AccountActivity :
private fun viewImage(view: View, uri: String) { private fun viewImage(view: View, uri: String) {
view.transitionName = uri view.transitionName = uri
startActivity( startActivity(
ViewMediaActivity.newSingleImageIntent(view.context, uri), ViewMediaActivityIntent(view.context, uri),
ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, uri).toBundle(), ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, uri).toBundle(),
) )
} }
@ -606,7 +611,7 @@ class AccountActivity :
binding.accountFollowButton.setOnClickListener { binding.accountFollowButton.setOnClickListener {
if (viewModel.isSelf) { if (viewModel.isSelf) {
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java) val intent = EditProfileActivityIntent(this@AccountActivity)
startActivity(intent) startActivity(intent)
return@setOnClickListener return@setOnClickListener
} }
@ -879,26 +884,25 @@ class AccountActivity :
private fun mention() { private fun mention() {
loadedAccount?.let { loadedAccount?.let {
val options = if (viewModel.isSelf) { val options = if (viewModel.isSelf) {
ComposeActivity.ComposeOptions(kind = ComposeActivity.ComposeKind.NEW) ComposeOptions(kind = ComposeOptions.ComposeKind.NEW)
} else { } else {
ComposeActivity.ComposeOptions( ComposeOptions(
mentionedUsernames = setOf(it.username), 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) startActivity(intent)
} }
} }
override fun onViewTag(tag: String) { override fun onViewTag(tag: String) {
val intent = StatusListActivity.newHashtagIntent(this, tag) val intent = StatusListActivityIntent.hashtag(this, tag)
startActivityWithSlideInAnimation(intent) startActivityWithSlideInAnimation(intent)
} }
override fun onViewAccount(id: String) { override fun onViewAccount(id: String) {
val intent = Intent(this, AccountActivity::class.java) val intent = AccountActivityIntent(this, id)
intent.putExtra("id", id)
startActivityWithSlideInAnimation(intent) startActivityWithSlideInAnimation(intent)
} }
@ -979,7 +983,7 @@ class AccountActivity :
} }
R.id.action_report -> { R.id.action_report -> {
loadedAccount?.let { loadedAccount -> loadedAccount?.let { loadedAccount ->
startActivity(ReportActivity.getIntent(this, viewModel.accountId, loadedAccount.username)) startActivity(ReportActivityIntent(this, viewModel.accountId, loadedAccount.username))
} }
return true return true
} }
@ -999,14 +1003,6 @@ class AccountActivity :
} }
companion object { companion object {
private const val KEY_ACCOUNT_ID = "id"
private val argbEvaluator = ArgbEvaluator() 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.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R import app.pachli.R
import app.pachli.ViewMediaActivity
import app.pachli.core.accounts.AccountManager 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.network.model.Attachment
import app.pachli.core.preferences.PrefKeys import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.SharedPreferencesRepository import app.pachli.core.preferences.SharedPreferencesRepository
@ -43,7 +44,6 @@ import app.pachli.util.hide
import app.pachli.util.openLink import app.pachli.util.openLink
import app.pachli.util.show import app.pachli.util.show
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
@ -175,7 +175,7 @@ class AccountMediaFragment :
Attachment.Type.VIDEO, Attachment.Type.VIDEO,
Attachment.Type.AUDIO, Attachment.Type.AUDIO,
-> { -> {
val intent = ViewMediaActivity.newIntent(context, attachmentsFromSameStatus, currentIndex) val intent = ViewMediaActivityIntent(requireContext(), attachmentsFromSameStatus, currentIndex)
if (activity != null) { if (activity != null) {
val url = selected.attachment.url val url = selected.attachment.url
ViewCompat.setTransitionName(view, url) ViewCompat.setTransitionName(view, url)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,8 +16,6 @@
package app.pachli.components.announcements package app.pachli.components.announcements
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -29,9 +27,9 @@ import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import app.pachli.BottomSheetActivity import app.pachli.BottomSheetActivity
import app.pachli.R import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.adapter.EmojiAdapter import app.pachli.adapter.EmojiAdapter
import app.pachli.adapter.OnEmojiSelectedListener import app.pachli.adapter.OnEmojiSelectedListener
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.core.preferences.PrefKeys import app.pachli.core.preferences.PrefKeys
import app.pachli.databinding.ActivityAnnouncementsBinding import app.pachli.databinding.ActivityAnnouncementsBinding
import app.pachli.util.Error import app.pachli.util.Error
@ -185,7 +183,7 @@ class AnnouncementsActivity :
} }
override fun onViewTag(tag: String) { override fun onViewTag(tag: String) {
val intent = StatusListActivity.newHashtagIntent(this, tag) val intent = StatusListActivityIntent.hashtag(this, tag)
startActivityWithSlideInAnimation(intent) startActivityWithSlideInAnimation(intent)
} }
@ -196,8 +194,4 @@ class AnnouncementsActivity :
override fun onViewUrl(url: String) { override fun onViewUrl(url: String) {
viewUrl(url) 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.Manifest
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.ClipData import android.content.ClipData
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
@ -28,7 +27,6 @@ import android.graphics.PorterDuffColorFilter
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.provider.MediaStore import android.provider.MediaStore
import android.text.Spanned import android.text.Spanned
import android.text.style.URLSpan 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.compose.view.ComposeScheduleView
import app.pachli.components.instanceinfo.InstanceInfoRepository import app.pachli.components.instanceinfo.InstanceInfoRepository
import app.pachli.core.database.model.AccountEntity 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.Attachment
import app.pachli.core.network.model.Emoji 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.network.model.Status
import app.pachli.core.preferences.PrefKeys import app.pachli.core.preferences.PrefKeys
import app.pachli.core.preferences.PrefKeys.APP_THEME 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.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -125,6 +123,10 @@ import java.util.Locale
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/**
* Compose a status, either by creating one from scratch, or by editing an existing
* status, draft, or scheduled status.
*/
@AndroidEntryPoint @AndroidEntryPoint
class ComposeActivity : class ComposeActivity :
BaseActivity(), BaseActivity(),
@ -233,7 +235,7 @@ class ComposeActivity :
/* If the composer is started up as a reply to another post, override the "starting" state /* 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. */ * 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) viewModel.setup(composeOptions)
setupButtons() setupButtons()
@ -1297,60 +1299,6 @@ class ComposeActivity :
viewModel.updateDescription(localId, description) 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 { companion object {
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 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 SCHEDULED_TIME_KEY = "SCHEDULE"
private const val CONTENT_WARNING_VISIBLE_KEY = "CONTENT_WARNING_VISIBLE" 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 { fun canHandleMimeType(mimeType: String?): Boolean {
return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain") 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.core.net.toUri
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.pachli.components.compose.ComposeActivity.ComposeKind
import app.pachli.components.compose.ComposeActivity.QueuedMedia import app.pachli.components.compose.ComposeActivity.QueuedMedia
import app.pachli.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult import app.pachli.components.compose.ComposeAutoCompleteAdapter.AutocompleteResult
import app.pachli.components.drafts.DraftHelper 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.components.search.SearchType
import app.pachli.core.accounts.AccountManager import app.pachli.core.accounts.AccountManager
import app.pachli.core.common.string.randomAlphanumericString 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.Attachment
import app.pachli.core.network.model.Emoji import app.pachli.core.network.model.Emoji
import app.pachli.core.network.model.NewPoll 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) { if (setupComplete) {
return return
} }

View File

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

View File

@ -17,7 +17,6 @@
package app.pachli.components.drafts package app.pachli.components.drafts
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
@ -26,8 +25,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.R import app.pachli.R
import app.pachli.components.compose.ComposeActivity
import app.pachli.core.database.model.DraftEntity 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.core.network.parseAsMastodonHtml
import app.pachli.databinding.ActivityDraftsBinding import app.pachli.databinding.ActivityDraftsBinding
import app.pachli.db.DraftsAlert import app.pachli.db.DraftsAlert
@ -106,7 +106,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
viewModel.getStatus(draft.inReplyToId!!) viewModel.getStatus(draft.inReplyToId!!)
.fold( .fold(
{ status -> { status ->
val composeOptions = ComposeActivity.ComposeOptions( val composeOptions = ComposeOptions(
draftId = draft.id, draftId = draft.id,
content = draft.content, content = draft.content,
contentWarning = draft.contentWarning, contentWarning = draft.contentWarning,
@ -120,12 +120,12 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
scheduledAt = draft.scheduledAt, scheduledAt = draft.scheduledAt,
language = draft.language, language = draft.language,
statusId = draft.statusId, statusId = draft.statusId,
kind = ComposeActivity.ComposeKind.EDIT_DRAFT, kind = ComposeOptions.ComposeKind.EDIT_DRAFT,
) )
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
startActivity(ComposeActivity.startIntent(context, composeOptions)) startActivity(ComposeActivityIntent(context, composeOptions))
}, },
{ throwable -> { throwable ->
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
@ -147,7 +147,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
} }
private fun openDraftWithoutReply(draft: DraftEntity) { private fun openDraftWithoutReply(draft: DraftEntity) {
val composeOptions = ComposeActivity.ComposeOptions( val composeOptions = ComposeOptions(
draftId = draft.id, draftId = draft.id,
content = draft.content, content = draft.content,
contentWarning = draft.contentWarning, contentWarning = draft.contentWarning,
@ -158,10 +158,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
scheduledAt = draft.scheduledAt, scheduledAt = draft.scheduledAt,
language = draft.language, language = draft.language,
statusId = draft.statusId, 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) { override fun onDeleteDraft(draft: DraftEntity) {
@ -172,8 +172,4 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
} }
.show() .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 android.widget.ArrayAdapter
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.IntentCompat
import androidx.core.view.size import androidx.core.view.size
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.R import app.pachli.R
import app.pachli.appstore.EventHub import app.pachli.appstore.EventHub
import app.pachli.core.navigation.EditFilterActivityIntent
import app.pachli.core.network.model.Filter import app.pachli.core.network.model.Filter
import app.pachli.core.network.model.FilterKeyword import app.pachli.core.network.model.FilterKeyword
import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.MastodonApi
@ -32,6 +32,9 @@ import retrofit2.HttpException
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
/**
* Edit a single server-side filter.
*/
@AndroidEntryPoint @AndroidEntryPoint
class EditFilterActivity : BaseActivity() { class EditFilterActivity : BaseActivity() {
@Inject @Inject
@ -50,7 +53,7 @@ class EditFilterActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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()) filter = originalFilter ?: Filter("", "", listOf(), null, Filter.Action.WARN.action, listOf())
binding.apply { binding.apply {
contextSwitches = mapOf( contextSwitches = mapOf(
@ -295,8 +298,6 @@ class EditFilterActivity : BaseActivity() {
} }
companion object { companion object {
const val FILTER_TO_EDIT = "FilterToEdit"
// Mastodon *stores* the absolute date in the filter, // Mastodon *stores* the absolute date in the filter,
// but create/edit take a number of seconds (relative to the time the operation is posted) // 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? { fun getSecondsForDurationIndex(index: Int, context: Context?, default: Date? = null): Int? {

View File

@ -1,12 +1,12 @@
package app.pachli.components.filters package app.pachli.components.filters
import android.content.DialogInterface.BUTTON_POSITIVE import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.R import app.pachli.R
import app.pachli.core.navigation.EditFilterActivityIntent
import app.pachli.core.network.model.Filter import app.pachli.core.network.model.Filter
import app.pachli.databinding.ActivityFiltersBinding import app.pachli.databinding.ActivityFiltersBinding
import app.pachli.util.hide import app.pachli.util.hide
@ -95,11 +95,7 @@ class FiltersActivity : BaseActivity(), FiltersListener {
} }
private fun launchEditFilterActivity(filter: Filter? = null) { private fun launchEditFilterActivity(filter: Filter? = null) {
val intent = Intent(this, EditFilterActivity::class.java).apply { val intent = EditFilterActivityIntent(this, filter)
if (filter != null) {
putExtra(EditFilterActivity.FILTER_TO_EDIT, filter)
}
}
startActivity(intent) startActivity(intent)
overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) 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 androidx.lifecycle.lifecycleScope
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.BuildConfig import app.pachli.BuildConfig
import app.pachli.MainActivity
import app.pachli.R 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.model.AccessToken
import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.getNonNullString import app.pachli.core.preferences.getNonNullString
@ -48,7 +49,11 @@ import okhttp3.HttpUrl
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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 @AndroidEntryPoint
class LoginActivity : BaseActivity() { class LoginActivity : BaseActivity() {
@ -318,7 +323,7 @@ class LoginActivity : BaseActivity() {
newAccount = newAccount, 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 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent) startActivity(intent)
finish() finish()
@ -343,33 +348,19 @@ class LoginActivity : BaseActivity() {
} }
private fun isAdditionalLogin(): Boolean { 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 { private fun isAccountMigration(): Boolean {
return intent.getIntExtra(LOGIN_MODE, MODE_DEFAULT) == MODE_MIGRATION return LoginActivityIntent.getLoginMode(intent) == LoginActivityIntent.LoginMode.MIGRATION
} }
companion object { companion object {
private const val OAUTH_SCOPES = "read write follow push" private const val OAUTH_SCOPES = "read write follow push"
private const val LOGIN_MODE = "LOGIN_MODE"
private const val DOMAIN = "domain" private const val DOMAIN = "domain"
private const val CLIENT_ID = "clientId" private const val CLIENT_ID = "clientId"
private const val CLIENT_SECRET = "clientSecret" 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. */ /** Make sure the user-entered text is just a fully-qualified domain name. */
private fun canonicalizeDomain(domain: String): String { private fun canonicalizeDomain(domain: String): String {
// Strip any schemes out. // Strip any schemes out.

View File

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

View File

@ -16,6 +16,7 @@
*/ */
package app.pachli.components.notifications package app.pachli.components.notifications
import android.annotation.SuppressLint
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationChannelGroup import android.app.NotificationChannelGroup
import android.app.NotificationManager import android.app.NotificationManager
@ -44,10 +45,10 @@ import app.pachli.MainActivity
import app.pachli.MainActivity.Companion.composeIntent import app.pachli.MainActivity.Companion.composeIntent
import app.pachli.MainActivity.Companion.openNotificationIntent import app.pachli.MainActivity.Companion.openNotificationIntent
import app.pachli.R import app.pachli.R
import app.pachli.components.compose.ComposeActivity
import app.pachli.core.accounts.AccountManager import app.pachli.core.accounts.AccountManager
import app.pachli.core.common.string.unicodeWrap import app.pachli.core.common.string.unicodeWrap
import app.pachli.core.database.model.AccountEntity 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.model.Notification
import app.pachli.core.network.parseAsMastodonHtml import app.pachli.core.network.parseAsMastodonHtml
import app.pachli.receiver.SendStatusBroadcastReceiver import app.pachli.receiver.SendStatusBroadcastReceiver
@ -382,6 +383,9 @@ private fun getStatusReplyIntent(
} }
mentionedUsernames.removeAll(setOf(account.username)) mentionedUsernames.removeAll(setOf(account.username))
mentionedUsernames = ArrayList(LinkedHashSet(mentionedUsernames)) mentionedUsernames = ArrayList(LinkedHashSet(mentionedUsernames))
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val replyIntent = Intent(context, SendStatusBroadcastReceiver::class.java) val replyIntent = Intent(context, SendStatusBroadcastReceiver::class.java)
.setAction(REPLY_ACTION) .setAction(REPLY_ACTION)
.putExtra(KEY_SENDER_ACCOUNT_ID, account.id) .putExtra(KEY_SENDER_ACCOUNT_ID, account.id)
@ -417,16 +421,17 @@ private fun getStatusComposeIntent(
mentionedUsernames.add(mentionedUsername) mentionedUsernames.add(mentionedUsername)
} }
} }
val composeOptions = ComposeActivity.ComposeOptions() val composeOptions = ComposeOptions(
composeOptions.inReplyToId = inReplyToId inReplyToId = inReplyToId,
composeOptions.replyVisibility = replyVisibility replyVisibility = replyVisibility,
composeOptions.contentWarning = contentWarning contentWarning = contentWarning,
composeOptions.replyingStatusAuthor = citedLocalAuthor replyingStatusAuthor = citedLocalAuthor,
composeOptions.replyingStatusContent = citedText replyingStatusContent = citedText,
composeOptions.mentionedUsernames = mentionedUsernames mentionedUsernames = mentionedUsernames,
composeOptions.modifiedInitialState = true modifiedInitialState = true,
composeOptions.language = language language = language,
composeOptions.kind = ComposeActivity.ComposeKind.NEW kind = ComposeOptions.ComposeKind.NEW,
)
val composeIntent = val composeIntent =
composeIntent(context, composeOptions, account.id, body.id, account.id.toInt()) composeIntent(context, composeOptions, account.id, body.id, account.id.toInt())
composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 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.R
import app.pachli.adapter.StatusBaseViewHolder import app.pachli.adapter.StatusBaseViewHolder
import app.pachli.components.timeline.TimelineLoadStateAdapter 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.Filter
import app.pachli.core.network.model.Notification import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.Status 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.show
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import app.pachli.util.visible import app.pachli.util.visible
import app.pachli.viewdata.AttachmentViewData.Companion.list
import app.pachli.viewdata.NotificationViewData import app.pachli.viewdata.NotificationViewData
import at.connyduck.sparkbutton.helpers.Utils import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors

View File

@ -22,9 +22,10 @@ import android.os.Build
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import app.pachli.R import app.pachli.R
import app.pachli.components.login.LoginActivity
import app.pachli.core.accounts.AccountManager import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity 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.model.Notification
import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.MastodonApi
import app.pachli.core.preferences.SharedPreferencesRepository import app.pachli.core.preferences.SharedPreferencesRepository
@ -78,7 +79,12 @@ private fun showMigrationExplanationDialog(
if (currentAccountNeedsMigration(accountManager)) { if (currentAccountNeedsMigration(accountManager)) {
setMessage(R.string.dialog_push_notification_migration) setMessage(R.string.dialog_push_notification_migration)
setPositiveButton(R.string.title_migration_relogin) { _, _ -> setPositiveButton(R.string.title_migration_relogin) { _, _ ->
context.startActivity(LoginActivity.getIntent(context, LoginActivity.MODE_MIGRATION)) context.startActivity(
LoginActivityIntent(
context,
LoginMode.MIGRATION,
),
)
} }
} else { } else {
setMessage(R.string.dialog_push_notification_migration_other_accounts) 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.BaseActivity
import app.pachli.BuildConfig import app.pachli.BuildConfig
import app.pachli.R import app.pachli.R
import app.pachli.TabPreferenceActivity
import app.pachli.appstore.EventHub 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.components.notifications.currentAccountNeedsMigration
import app.pachli.core.accounts.AccountManager 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.Account
import app.pachli.core.network.model.Status import app.pachli.core.network.model.Status
import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.network.retrofit.MastodonApi
@ -90,7 +93,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_tab_preferences) setTitle(R.string.title_tab_preferences)
setIcon(R.drawable.ic_tabs) setIcon(R.drawable.ic_tabs)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = Intent(context, TabPreferenceActivity::class.java) val intent = TabPreferenceActivityIntent(context)
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition( activity?.overridePendingTransition(
R.anim.slide_from_right, R.anim.slide_from_right,
@ -104,7 +107,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_followed_hashtags) setTitle(R.string.title_followed_hashtags)
setIcon(R.drawable.ic_hashtag) setIcon(R.drawable.ic_hashtag)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = Intent(context, FollowedTagsActivity::class.java) val intent = FollowedTagsActivityIntent(context)
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition( activity?.overridePendingTransition(
R.anim.slide_from_right, R.anim.slide_from_right,
@ -118,8 +121,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.action_view_mutes) setTitle(R.string.action_view_mutes)
setIcon(R.drawable.ic_mute_24dp) setIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java) val intent = AccountListActivityIntent(context, AccountListActivityIntent.Kind.MUTES)
intent.putExtra("type", AccountListActivity.Type.MUTES)
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition( activity?.overridePendingTransition(
R.anim.slide_from_right, R.anim.slide_from_right,
@ -133,8 +135,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.action_view_blocks) setTitle(R.string.action_view_blocks)
icon = makeIcon(GoogleMaterial.Icon.gmd_block) icon = makeIcon(GoogleMaterial.Icon.gmd_block)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = Intent(context, AccountListActivity::class.java) val intent = AccountListActivityIntent(context, AccountListActivityIntent.Kind.BLOCKS)
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition( activity?.overridePendingTransition(
R.anim.slide_from_right, R.anim.slide_from_right,
@ -148,7 +149,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_domain_mutes) setTitle(R.string.title_domain_mutes)
setIcon(R.drawable.ic_mute_24dp) setIcon(R.drawable.ic_mute_24dp)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = Intent(context, InstanceListActivity::class.java) val intent = InstanceListActivityIntent(context)
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition( activity?.overridePendingTransition(
R.anim.slide_from_right, R.anim.slide_from_right,
@ -163,7 +164,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setTitle(R.string.title_migration_relogin) setTitle(R.string.title_migration_relogin)
setIcon(R.drawable.ic_logout) setIcon(R.drawable.ic_logout)
setOnPreferenceClickListener { setOnPreferenceClickListener {
val intent = LoginActivity.getIntent(context, LoginActivity.MODE_MIGRATION) val intent = LoginActivityIntent(context, LoginMode.MIGRATION)
(activity as BaseActivity).startActivityWithSlideInAnimation(intent) (activity as BaseActivity).startActivityWithSlideInAnimation(intent)
true true
} }
@ -280,7 +281,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
} else { } else {
activity?.let { activity?.let {
val intent = val intent =
PreferencesActivity.newIntent(it, PreferencesActivity.NOTIFICATION_PREFERENCES) PreferencesActivityIntent(it, PreferenceScreen.NOTIFICATION)
it.startActivity(intent) it.startActivity(intent)
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
} }
@ -346,7 +347,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
} }
private fun launchFilterActivity() { private fun launchFilterActivity() {
val intent = Intent(context, FiltersActivity::class.java) val intent = FiltersActivityIntent(requireContext())
activity?.startActivity(intent) activity?.startActivity(intent)
activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) activity?.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
} }

View File

@ -16,7 +16,6 @@
package app.pachli.components.preference package app.pachli.components.preference
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@ -26,9 +25,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.MainActivity
import app.pachli.R import app.pachli.R
import app.pachli.appstore.EventHub 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
import app.pachli.core.preferences.PrefKeys.APP_THEME import app.pachli.core.preferences.PrefKeys.APP_THEME
import app.pachli.core.preferences.getNonNullString import app.pachli.core.preferences.getNonNullString
@ -41,6 +42,9 @@ import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
/**
* Show specific preferences.
*/
@AndroidEntryPoint @AndroidEntryPoint
class PreferencesActivity : class PreferencesActivity :
BaseActivity(), BaseActivity(),
@ -55,7 +59,7 @@ class PreferencesActivity :
* Either the back stack activities need to all be recreated, or do the easier thing, which * 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 * is hijack the back button press and use it to launch a new MainActivity and clear the
* back stack. */ * 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 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivityWithSlideInAnimation(intent) startActivityWithSlideInAnimation(intent)
} }
@ -73,15 +77,15 @@ class PreferencesActivity :
setDisplayShowHomeEnabled(true) setDisplayShowHomeEnabled(true)
} }
val preferenceType = intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0) val preferenceType = PreferencesActivityIntent.getPreferenceType(intent)
val fragmentTag = "preference_fragment_$preferenceType" val fragmentTag = "preference_fragment_$preferenceType"
val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag) val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag)
?: when (preferenceType) { ?: when (preferenceType) {
GENERAL_PREFERENCES -> PreferencesFragment.newInstance() PreferenceScreen.GENERAL -> PreferencesFragment.newInstance()
ACCOUNT_PREFERENCES -> AccountPreferencesFragment.newInstance() PreferenceScreen.ACCOUNT -> AccountPreferencesFragment.newInstance()
NOTIFICATION_PREFERENCES -> NotificationPreferencesFragment.newInstance() PreferenceScreen.NOTIFICATION -> NotificationPreferencesFragment.newInstance()
else -> throw IllegalArgumentException("preferenceType not known") else -> throw IllegalArgumentException("preferenceType not known")
} }
@ -164,17 +168,6 @@ class PreferencesActivity :
} }
companion object { 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" 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 package app.pachli.components.report
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import app.pachli.BottomSheetActivity import app.pachli.BottomSheetActivity
import app.pachli.R import app.pachli.R
import app.pachli.components.report.adapter.ReportPagerAdapter import app.pachli.components.report.adapter.ReportPagerAdapter
import app.pachli.core.navigation.ReportActivityIntent
import app.pachli.databinding.ActivityReportBinding import app.pachli.databinding.ActivityReportBinding
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
/**
* Report a status or user.
*/
@AndroidEntryPoint @AndroidEntryPoint
class ReportActivity : BottomSheetActivity() { class ReportActivity : BottomSheetActivity() {
private val viewModel: ReportViewModel by viewModels() private val viewModel: ReportViewModel by viewModels()
@ -35,13 +37,13 @@ class ReportActivity : BottomSheetActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val accountId = intent?.getStringExtra(ACCOUNT_ID) val accountId = ReportActivityIntent.getAccountId(intent)
val accountUserName = intent?.getStringExtra(ACCOUNT_USERNAME) val accountUserName = ReportActivityIntent.getAccountUserName(intent)
if (accountId.isNullOrBlank() || accountUserName.isNullOrBlank()) { if (accountId.isBlank() || accountUserName.isBlank()) {
throw IllegalStateException("accountId ($accountId) or accountUserName ($accountUserName) is null") 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) setContentView(binding.root)
@ -115,19 +117,4 @@ class ReportActivity : BottomSheetActivity() {
private fun showStatusesPage() { private fun showStatusesPage() {
binding.wizard.currentItem = 0 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.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.R 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.ReportViewModel
import app.pachli.components.report.Screen import app.pachli.components.report.Screen
import app.pachli.components.report.adapter.AdapterHandler import app.pachli.components.report.adapter.AdapterHandler
import app.pachli.components.report.adapter.StatusesAdapter import app.pachli.components.report.adapter.StatusesAdapter
import app.pachli.core.accounts.AccountManager 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.Attachment
import app.pachli.core.network.model.Status import app.pachli.core.network.model.Status
import app.pachli.databinding.FragmentReportStatusesBinding import app.pachli.databinding.FragmentReportStatusesBinding
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import app.pachli.util.visible import app.pachli.util.visible
import app.pachli.viewdata.AttachmentViewData
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -82,7 +82,7 @@ class ReportStatusesFragment :
when (actionable.attachments[idx].type) { when (actionable.attachments[idx].type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> { Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val attachments = AttachmentViewData.list(actionable) val attachments = AttachmentViewData.list(actionable)
val intent = ViewMediaActivity.newIntent(context, attachments, idx) val intent = ViewMediaActivityIntent(requireContext(), attachments, idx)
if (v != null) { if (v != null) {
val url = actionable.attachments[idx].url val url = actionable.attachments[idx].url
ViewCompat.setTransitionName(v, url) ViewCompat.setTransitionName(v, url)
@ -209,9 +209,9 @@ class ReportStatusesFragment :
return viewModel.isStatusChecked(id) 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) override fun onViewUrl(url: String) = viewModel.checkClickedUrl(url)

View File

@ -16,8 +16,6 @@
package app.pachli.components.scheduled package app.pachli.components.scheduled
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -32,7 +30,8 @@ import app.pachli.BaseActivity
import app.pachli.R import app.pachli.R
import app.pachli.appstore.EventHub import app.pachli.appstore.EventHub
import app.pachli.appstore.StatusScheduledEvent 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.core.network.model.ScheduledStatus
import app.pachli.databinding.ActivityScheduledStatusBinding import app.pachli.databinding.ActivityScheduledStatusBinding
import app.pachli.util.hide import app.pachli.util.hide
@ -151,9 +150,9 @@ class ScheduledStatusActivity :
} }
override fun edit(item: ScheduledStatus) { override fun edit(item: ScheduledStatus) {
val intent = ComposeActivity.startIntent( val intent = ComposeActivityIntent(
this, this,
ComposeActivity.ComposeOptions( ComposeOptions(
scheduledTootId = item.id, scheduledTootId = item.id,
content = item.params.text, content = item.params.text,
contentWarning = item.params.spoilerText, contentWarning = item.params.spoilerText,
@ -162,7 +161,7 @@ class ScheduledStatusActivity :
visibility = item.params.visibility, visibility = item.params.visibility,
scheduledAt = item.scheduledAt, scheduledAt = item.scheduledAt,
sensitive = item.params.sensitive, sensitive = item.params.sensitive,
kind = ComposeActivity.ComposeKind.EDIT_SCHEDULED, kind = ComposeOptions.ComposeKind.EDIT_SCHEDULED,
), ),
) )
startActivity(intent) startActivity(intent)
@ -177,8 +176,4 @@ class ScheduledStatusActivity :
} }
.show() .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 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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import app.pachli.BottomSheetActivity import app.pachli.BottomSheetActivity
import app.pachli.R import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.account.AccountActivity
import app.pachli.components.search.SearchViewModel 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.core.network.retrofit.MastodonApi
import app.pachli.databinding.FragmentSearchBinding import app.pachli.databinding.FragmentSearchBinding
import app.pachli.interfaces.LinkListener import app.pachli.interfaces.LinkListener
@ -141,11 +141,11 @@ abstract class SearchFragment<T : Any> :
} }
override fun onViewAccount(id: String) { override fun onViewAccount(id: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id)) bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivityIntent(requireContext(), id))
} }
override fun onViewTag(tag: String) { override fun onViewTag(tag: String) {
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag)) bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivityIntent.hashtag(requireContext(), tag))
} }
override fun onViewUrl(url: String) { override fun onViewUrl(url: String) {

View File

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

View File

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

View File

@ -18,8 +18,6 @@
package app.pachli.components.trending package app.pachli.components.trending
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
@ -86,10 +84,6 @@ class TrendingActivity : BottomSheetActivity(), AppBarLayoutHost, MenuProvider {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return super.onOptionsItemSelected(menuItem) return super.onOptionsItemSelected(menuItem)
} }
companion object {
fun getIntent(context: Context) = Intent(context, TrendingActivity::class.java)
}
} }
class TrendingFragmentAdapter(val activity: FragmentActivity) : FragmentStateAdapter(activity) { 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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.R import app.pachli.R
import app.pachli.StatusListActivity
import app.pachli.components.trending.viewmodel.TrendingTagsViewModel import app.pachli.components.trending.viewmodel.TrendingTagsViewModel
import app.pachli.core.navigation.StatusListActivityIntent
import app.pachli.databinding.FragmentTrendingTagsBinding import app.pachli.databinding.FragmentTrendingTagsBinding
import app.pachli.interfaces.ActionButtonActivity import app.pachli.interfaces.ActionButtonActivity
import app.pachli.interfaces.AppBarLayoutHost import app.pachli.interfaces.AppBarLayoutHost
@ -173,7 +173,7 @@ class TrendingTagsFragment :
fun onViewTag(tag: String) { fun onViewTag(tag: String) {
(requireActivity() as BaseActivity).startActivityWithSlideInAnimation( (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(
StatusListActivity.newHashtagIntent( StatusListActivityIntent.hashtag(
requireContext(), requireContext(),
tag, tag,
), ),

View File

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

View File

@ -34,9 +34,9 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import app.pachli.BaseActivity import app.pachli.BaseActivity
import app.pachli.R 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.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.databinding.FragmentViewThreadBinding
import app.pachli.fragment.SFragment import app.pachli.fragment.SFragment
import app.pachli.interfaces.StatusActionListener import app.pachli.interfaces.StatusActionListener
@ -45,7 +45,6 @@ import app.pachli.util.hide
import app.pachli.util.openLink import app.pachli.util.openLink
import app.pachli.util.show import app.pachli.util.show
import app.pachli.util.viewBinding import app.pachli.util.viewBinding
import app.pachli.viewdata.AttachmentViewData.Companion.list
import app.pachli.viewdata.StatusViewData import app.pachli.viewdata.StatusViewData
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
@ -366,13 +365,13 @@ class ViewThreadFragment :
override fun onShowReblogs(position: Int) { override fun onShowReblogs(position: Int) {
val statusId = adapter.currentList[position].id 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) (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent)
} }
override fun onShowFavs(position: Int) { override fun onShowFavs(position: Int) {
val statusId = adapter.currentList[position].id 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) (requireActivity() as BaseActivity).startActivityWithSlideInAnimation(intent)
} }
@ -423,7 +422,7 @@ class ViewThreadFragment :
private const val ID_EXTRA = "id" private const val ID_EXTRA = "id"
private const val URL_EXTRA = "url" 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 arguments = Bundle(2)
val fragment = ViewThreadFragment() val fragment = ViewThreadFragment()
arguments.putString(ID_EXTRA, id) arguments.putString(ID_EXTRA, id)

View File

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

View File

@ -23,9 +23,9 @@ import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.pachli.R import app.pachli.R
import app.pachli.components.drafts.DraftsActivity
import app.pachli.core.accounts.AccountManager import app.pachli.core.accounts.AccountManager
import app.pachli.core.database.dao.DraftDao import app.pachli.core.database.dao.DraftDao
import app.pachli.core.navigation.DraftsActivityIntent
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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 -> .setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int ->
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts
val intent = DraftsActivity.newIntent(context) val intent = DraftsActivityIntent(context)
context.startActivity(intent) context.startActivity(intent)
} }
.setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int -> .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.BottomSheetActivity
import app.pachli.PostLookupFallbackBehavior import app.pachli.PostLookupFallbackBehavior
import app.pachli.R 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.accounts.AccountManager
import app.pachli.core.database.model.AccountEntity import app.pachli.core.database.model.AccountEntity
import app.pachli.core.database.model.TranslationState 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.ServerOperation
import app.pachli.core.network.model.Attachment import app.pachli.core.network.model.Attachment
import app.pachli.core.network.model.Status import app.pachli.core.network.model.Status
@ -61,7 +61,6 @@ import app.pachli.network.ServerCapabilitiesRepository
import app.pachli.usecase.TimelineCases import app.pachli.usecase.TimelineCases
import app.pachli.util.openLink import app.pachli.util.openLink
import app.pachli.view.showMuteAccountDialog import app.pachli.view.showMuteAccountDialog
import app.pachli.viewdata.AttachmentViewData
import app.pachli.viewdata.StatusViewData import app.pachli.viewdata.StatusViewData
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure import at.connyduck.calladapter.networkresult.onFailure
@ -163,10 +162,10 @@ abstract class SFragment : Fragment() {
replyingStatusAuthor = account.localUsername, replyingStatusAuthor = account.localUsername,
replyingStatusContent = actionableStatus.content.parseAsMastodonHtml().toString(), replyingStatusContent = actionableStatus.content.parseAsMastodonHtml().toString(),
language = actionableStatus.language, language = actionableStatus.language,
kind = ComposeActivity.ComposeKind.NEW, kind = ComposeOptions.ComposeKind.NEW,
) )
val intent = startIntent(requireContext(), composeOptions) val intent = ComposeActivityIntent(requireContext(), composeOptions)
requireActivity().startActivity(intent) requireActivity().startActivity(intent)
} }
@ -379,7 +378,7 @@ abstract class SFragment : Fragment() {
val (attachment) = attachments[urlIndex] val (attachment) = attachments[urlIndex]
when (attachment.type) { when (attachment.type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> { 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) { if (view != null) {
val url = attachment.url val url = attachment.url
view.transitionName = url view.transitionName = url
@ -400,11 +399,11 @@ abstract class SFragment : Fragment() {
} }
protected fun viewTag(tag: String) { protected fun viewTag(tag: String) {
startActivity(newHashtagIntent(requireContext(), tag)) startActivity(StatusListActivityIntent.hashtag(requireContext(), tag))
} }
private fun openReportPage(accountId: String, accountUsername: String, statusId: String) { 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) { private fun showConfirmDeleteDialog(id: String, position: Int) {
@ -455,9 +454,9 @@ abstract class SFragment : Fragment() {
modifiedInitialState = true, modifiedInitialState = true,
language = sourceStatus.language, language = sourceStatus.language,
poll = sourceStatus.poll?.toNewPoll(sourceStatus.createdAt), poll = sourceStatus.poll?.toNewPoll(sourceStatus.createdAt),
kind = ComposeActivity.ComposeKind.NEW, kind = ComposeOptions.ComposeKind.NEW,
) )
startActivity(startIntent(requireContext(), composeOptions)) startActivity(ComposeActivityIntent(requireContext(), composeOptions))
}, },
{ error: Throwable? -> { error: Throwable? ->
Timber.w(error, "error deleting status") Timber.w(error, "error deleting status")
@ -485,9 +484,9 @@ abstract class SFragment : Fragment() {
language = status.language, language = status.language,
statusId = source.id, statusId = source.id,
poll = status.poll?.toNewPoll(status.createdAt), 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( Snackbar.make(

View File

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

View File

@ -1,5 +1,6 @@
package app.pachli.service package app.pachli.service
import android.annotation.SuppressLint
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
@ -373,6 +374,8 @@ class SendStatusService : Service() {
} }
private fun cancelSendingIntent(statusId: Int): PendingIntent { private fun cancelSendingIntent(statusId: Int): PendingIntent {
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val intent = Intent(this, SendStatusService::class.java) val intent = Intent(this, SendStatusService::class.java)
intent.putExtra(KEY_CANCEL, statusId) intent.putExtra(KEY_CANCEL, statusId)
return PendingIntent.getService( return PendingIntent.getService(
@ -428,6 +431,8 @@ class SendStatusService : Service() {
context: Context, context: Context,
statusToSend: StatusToSend, statusToSend: StatusToSend,
): Intent { ): Intent {
// TODO: Revisit suppressing this when this file is moved
@SuppressLint("IntentDetector")
val intent = Intent(context, SendStatusService::class.java) val intent = Intent(context, SendStatusService::class.java)
intent.putExtra(KEY_STATUS, statusToSend) 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.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import app.pachli.MainActivity
import app.pachli.R import app.pachli.R
import app.pachli.core.database.model.AccountEntity import app.pachli.core.database.model.AccountEntity
import app.pachli.core.navigation.MainActivityIntent
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
@ -67,7 +67,7 @@ fun updateShortcut(context: Context, account: AccountEntity) {
.build() .build()
// This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different // 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 action = Intent.ACTION_SEND
type = "text/plain" type = "text/plain"
putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString()) 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.AccountEntity
import app.pachli.core.database.model.TabKind import app.pachli.core.database.model.TabKind
import app.pachli.core.database.model.defaultTabs 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.Account
import app.pachli.core.network.model.Notification import app.pachli.core.network.model.Notification
import app.pachli.core.network.model.TimelineAccount import app.pachli.core.network.model.TimelineAccount
@ -176,7 +177,7 @@ class MainActivityTest {
nextActivity.component, nextActivity.component,
) )
assertEquals( assertEquals(
AccountListActivity.Type.FOLLOW_REQUESTS, AccountListActivityIntent.Kind.FOLLOW_REQUESTS,
nextActivity.getSerializableExtra("type"), nextActivity.getSerializableExtra("type"),
) )
} }

View File

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

View File

@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.ktlint) apply false alias(libs.plugins.ktlint) apply false
alias(libs.plugins.aboutlibraries) apply false alias(libs.plugins.aboutlibraries) apply false
alias(libs.plugins.hilt) apply false alias(libs.plugins.hilt) apply false
alias(libs.plugins.quadrant) apply false
} }
allprojects { 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") @Suppress("UnstableApiUsage")
class LintRegistry : IssueRegistry() { class LintRegistry : IssueRegistry() {
override val issues: List<Issue> override val issues: List<Issue>
get() = listOf(AndroidxToolbarDetector.ISSUE) get() = listOf(
AndroidxToolbarDetector.ISSUE,
IntentDetector.ISSUE,
)
override val api: Int override val api: Int
get() = CURRENT_API 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) implementation(projects.core.preferences)
// Because of the use of @SerializedName in DraftEntity // 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. * This file is a part of Pachli.
* *
@ -14,7 +15,7 @@
* see <http://www.gnu.org/licenses>. * see <http://www.gnu.org/licenses>.
*/ */
package app.pachli.viewdata package app.pachli.core.navigation
import android.os.Parcelable import android.os.Parcelable
import app.pachli.core.network.model.Attachment 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" mockito-kotlin = "5.2.1"
networkresult-calladapter = "1.0.0" networkresult-calladapter = "1.0.0"
okhttp = "4.12.0" okhttp = "4.12.0"
quadrant = "1.7"
retrofit = "2.9.0" retrofit = "2.9.0"
robolectric = "4.11.1" robolectric = "4.11.1"
rxandroid3 = "3.0.2" 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" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
ktlint = "org.jlleitschuh.gradle.ktlint:11.6.1" ktlint = "org.jlleitschuh.gradle.ktlint:11.6.1"
room = { id = "androidx.room", version.ref = "androidx-room" } room = { id = "androidx.room", version.ref = "androidx-room" }
quadrant = { id = "com.gaelmarhic.quadrant", version.ref = "quadrant" }
# Plugins defined by this project # Plugins defined by this project
pachli-android-application = { id = "pachli.android.application", version = "unspecified" } pachli-android-application = { id = "pachli.android.application", version = "unspecified" }

View File

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