Browse Source

Merge remote-tracking branch 'tuskyapp/develop'

pull/10/head
kyori19 3 years ago
parent
commit
762b2225ca
  1. 4
      README.md
  2. 150
      app/build.gradle
  3. 20
      app/src/blue/res/xml-v25/shortcuts.xml
  4. 20
      app/src/green/res/xml-v25/shortcuts.xml
  5. 20
      app/src/main/AndroidManifest.xml
  6. 13
      app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt
  7. 67
      app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt
  8. 3
      app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt
  9. 6
      app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
  10. 21
      app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt
  11. 89
      app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt
  12. 44
      app/src/main/java/com/keylesspalace/tusky/MainActivity.java
  13. 12
      app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt
  14. 11
      app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java
  15. 34
      app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
  16. 3
      app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt
  17. 6
      app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
  18. 8
      app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
  19. 8
      app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
  20. 8
      app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
  21. 6
      app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
  22. 9
      app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
  23. 2
      app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
  24. 2
      app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
  25. 2
      app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt
  26. 2
      app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt
  27. 25
      app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt
  28. 18
      app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt
  29. 4
      app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt
  30. 22
      app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt
  31. 2
      app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt
  32. 5
      app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
  33. 2
      app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
  34. 3
      app/src/main/java/com/keylesspalace/tusky/entity/Account.kt
  35. 11
      app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt
  36. 2
      app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
  37. 3
      app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
  38. 36
      app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
  39. 5
      app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt
  40. 2
      app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
  41. 6
      app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt
  42. 10
      app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt
  43. 120
      app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java
  44. 65
      app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt
  45. 17
      app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt
  46. 41
      app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt
  47. 46
      app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt
  48. 65
      app/src/main/java/com/keylesspalace/tusky/service/AccountChooserService.kt
  49. 33
      app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt
  50. 8
      app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt
  51. 2
      app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
  52. 5
      app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt
  53. 9
      app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java
  54. 5
      app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
  55. 102
      app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt
  56. 5
      app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt
  57. 39
      app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
  58. 3
      app/src/main/java/com/keylesspalace/tusky/view/ComposeScheduleView.java
  59. 45
      app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java
  60. 11
      app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt
  61. 5
      app/src/main/res/color/tab_icon_color.xml
  62. 6
      app/src/main/res/drawable-night/avatar_background.xml
  63. 6
      app/src/main/res/drawable/avatar_background.xml
  64. 10
      app/src/main/res/drawable/ic_menu_24dp.xml
  65. 9
      app/src/main/res/drawable/ic_reblog_active_24dp.xml
  66. 0
      app/src/main/res/drawable/ic_reblog_dark_24dp.xml
  67. 0
      app/src/main/res/drawable/ic_reblog_light_24dp.xml
  68. 9
      app/src/main/res/drawable/reblog_active.xml
  69. 5
      app/src/main/res/drawable/tab_page_margin_black.xml
  70. 5
      app/src/main/res/drawable/tab_page_margin_dark.xml
  71. 5
      app/src/main/res/drawable/tab_page_margin_light.xml
  72. 14
      app/src/main/res/layout-sw640dp/fragment_timeline.xml
  73. 5
      app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml
  74. 7
      app/src/main/res/layout-sw640dp/fragment_view_thread.xml
  75. 17
      app/src/main/res/layout/activity_about.xml
  76. 18
      app/src/main/res/layout/activity_account.xml
  77. 1
      app/src/main/res/layout/activity_login.xml
  78. 75
      app/src/main/res/layout/activity_main.xml
  79. 8
      app/src/main/res/layout/activity_search.xml
  80. 2
      app/src/main/res/layout/activity_view_media.xml
  81. 20
      app/src/main/res/layout/fragment_search.xml
  82. 19
      app/src/main/res/layout/fragment_timeline.xml
  83. 3
      app/src/main/res/layout/fragment_timeline_notifications.xml
  84. 15
      app/src/main/res/layout/fragment_view_image.xml
  85. 2
      app/src/main/res/layout/fragment_view_thread.xml
  86. 3
      app/src/main/res/layout/fragment_view_video.xml
  87. 8
      app/src/main/res/layout/item_follow.xml
  88. 4
      app/src/main/res/layout/item_status.xml
  89. 4
      app/src/main/res/layout/item_status_detailed.xml
  90. 67
      app/src/main/res/layout/item_status_notification.xml
  91. 3
      app/src/main/res/layout/toolbar_basic.xml
  92. 11
      app/src/main/res/values-ar/strings.xml
  93. 11
      app/src/main/res/values-bn-rIN/strings.xml
  94. 2
      app/src/main/res/values-ca/strings.xml
  95. 17
      app/src/main/res/values-cs/strings.xml
  96. 1
      app/src/main/res/values-cy/strings.xml
  97. 9
      app/src/main/res/values-de/strings.xml
  98. 2
      app/src/main/res/values-eo/strings.xml
  99. 36
      app/src/main/res/values-es/strings.xml
  100. 285
      app/src/main/res/values-eu/strings.xml

4
README.md

@ -5,7 +5,7 @@
Yuito is fork of [Tusky](https://github.com/tuskyapp/Tusky).
Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly.
Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is an ActivityPub federated social network. That means no single entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly.
## Features
@ -22,6 +22,8 @@ Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/
If you have any bug reports, feature requests or questions please open an issue or send us a toot at [@ars42525@odakyu.app](https://odakyu.app/@ars42525)!
For translating Tusky into your language, visit https://weblate.tusky.app/
### Head of development
This app was developed by [@ars42525@odakyu.app](https://odakyu.app/@ars42525).

150
app/build.gradle

@ -3,7 +3,9 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def getGitSha = { ->
apply from: "../instance-build.gradle"
def getGitSha = {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short', 'HEAD'
@ -13,19 +15,26 @@ def getGitSha = { ->
}
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId 'net.accelf.yuito'
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 29
versionCode 4
versionName '1.1.2'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
resValue "string", "app_name", APP_NAME
buildConfigField("String", "CUSTOM_LOGO_URL", "\"$CUSTOM_LOGO_URL\"")
buildConfigField("String", "CUSTOM_INSTANCE", "\"$CUSTOM_INSTANCE\"")
buildConfigField("String", "SUPPORT_ACCOUNT_URL", "\"$SUPPORT_ACCOUNT_URL\"")
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
}
}
@ -61,9 +70,6 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
androidExtensions {
experimental = true
}
testOptions {
unitTests {
returnDefaultValues = true
@ -74,7 +80,6 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
packagingOptions {
// Exclude unneeded files added by libraries
exclude 'LICENSE_OFL'
@ -94,8 +99,11 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
}
}
ext.daggerVersion = '2.24'
ext.roomVersion = '2.2.1'
ext.retrofitVersion = '2.6.0'
ext.okhttpVersion = '4.2.2'
ext.glideVersion = '4.10.0'
ext.daggerVersion = '2.25.2'
repositories {
maven {
@ -105,72 +113,84 @@ repositories {
// if libraries are changed here, they should also be changed in LicenseActivity
dependencies {
implementation('com.mikepenz:materialdrawer:6.1.2@aar') {
transitive = true
}
implementation 'androidx.core:core:1.0.2'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha10'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.1.0-alpha04'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0-beta01"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.browser:browser:1.0.0"
implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation "androidx.exifinterface:exifinterface:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.preference:preference:1.1.0"
implementation "androidx.sharetarget:sharetarget:1.0.0-beta01"
implementation "androidx.emoji:emoji:1.0.0"
implementation "androidx.emoji:emoji-appcompat:1.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
implementation "androidx.viewpager2:viewpager2:1.0.0-rc01"
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.room:room-rxjava2:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
implementation "com.google.android.material:material:1.1.0-beta01"
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0'
implementation 'org.conscrypt:conscrypt-android:2.2.1'
implementation 'com.github.connyduck:sparkbutton:2.0.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:google-material-typeface:3.0.1.3.original@aar'
implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') {
exclude group: 'com.android.support'
}
implementation 'com.evernote:android-job:1.2.6'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// EmojiCompat
implementation 'androidx.emoji:emoji:1.0.0'
implementation 'androidx.emoji:emoji-appcompat:1.0.0'
implementation 'de.c1710:filemojicompat:1.0.17'
// architecture components
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
//room
implementation 'androidx.room:room-runtime:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt 'androidx.room:room-compiler:2.1.0'
implementation 'androidx.room:room-rxjava2:2.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
implementation "org.conscrypt:conscrypt-android:2.2.1"
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion"
implementation "io.reactivex.rxjava2:rxjava:2.2.13"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxkotlin:2.4.0"
implementation "com.uber.autodispose:autodispose-android-archcomponents:1.4.0"
implementation "com.uber.autodispose:autodispose:1.4.0"
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
implementation "com.google.dagger:dagger-android:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.mockito:mockito-inline:3.0.0'
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
exclude group: 'com.android.support', module: 'support-annotations'
implementation "com.github.connyduck:sparkbutton:2.0.1"
implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation("com.mikepenz:materialdrawer:6.1.2@aar") {
transitive = true
}
implementation "com.mikepenz:google-material-typeface:3.0.1.3.original@aar"
implementation("com.theartofdev.edmodo:android-image-cropper:2.8.0") {
exclude group: "com.android.support"
}
implementation "com.evernote:android-job:1.4.2"
implementation "de.c1710:filemojicompat:1.0.17"
testImplementation "androidx.test.ext:junit:1.1.1"
testImplementation "org.robolectric:robolectric:4.3.1"
testImplementation "org.mockito:mockito-inline:3.1.0"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
androidTestImplementation("androidx.test.espresso:espresso-core:3.1.1", {
exclude group: "com.android.support", module: "support-annotations"
})
androidTestImplementation 'android.arch.persistence.room:testing:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'androidx.test.ext:junit:1.1.1'
debugImplementation 'im.dino:dbinspector:3.4.1@aar'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.4.0'
implementation 'com.uber.autodispose:autodispose:1.4.0'
implementation 'androidx.paging:paging-runtime-ktx:2.1.0'
//Glide
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.10.0'
//Add some useful extensions
implementation 'androidx.core:core-ktx:1.2.0-alpha01'
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
debugImplementation "im.dino:dbinspector:4.0.0@aar"
implementation 'net.accelf:easter:1.0.1'
}

20
app/src/blue/res/xml-v25/shortcuts.xml

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:icon="@mipmap/ic_shortcut_compose"
android:shortcutId="com.keylesspalace.tusky.Compose"
android:shortcutLongLabel="@string/compose_shortcut_long_label"
android:shortcutShortLabel="@string/compose_shortcut_short_label">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.keylesspalace.tusky.SplashActivity"
android:targetPackage="com.keylesspalace.tusky"/>
<intent
android:action="com.keylesspalace.tusky.Compose"
android:targetClass="com.keylesspalace.tusky.ComposeActivity"
android:targetPackage="com.keylesspalace.tusky"/>
</shortcut>
</shortcuts>

20
app/src/green/res/xml-v25/shortcuts.xml

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:icon="@mipmap/ic_shortcut_compose"
android:shortcutId="com.keylesspalace.tusky.Compose"
android:shortcutLongLabel="@string/compose_shortcut_long_label"
android:shortcutShortLabel="@string/compose_shortcut_short_label">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.keylesspalace.tusky.SplashActivity"
android:targetPackage="com.keylesspalace.tusky.test"/>
<intent
android:action="com.keylesspalace.tusky.Compose"
android:targetClass="com.keylesspalace.tusky.ComposeActivity"
android:targetPackage="com.keylesspalace.tusky.test"/>
</shortcut>
</shortcuts>

20
app/src/main/AndroidManifest.xml

@ -31,7 +31,8 @@
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
android:resource="@xml/share_shortcuts" />
</activity>
<activity
android:name=".SavedTootActivity"
@ -52,7 +53,7 @@
</activity>
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden">
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@ -91,7 +92,8 @@
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value="com.keylesspalace.tusky.service.AccountChooserService" />
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
</activity>
<activity
android:name=".ComposeActivity"
@ -106,7 +108,7 @@
android:theme="@style/TuskyBaseTheme" />
<activity
android:name=".AccountActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" />
<activity android:name=".EditProfileActivity" />
<activity android:name=".PreferencesActivity" />
<activity android:name=".FavouritesActivity" />
@ -153,16 +155,8 @@
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service android:name=".service.SendTootService" />
<service
android:name=".service.AccountChooserService"
android:label="@string/app_name"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE"
tools:targetApi="23">
<intent-filter>
<action android:name="android.service.chooser.ChooserTargetService" />
</intent-filter>
</service>
<provider
android:name="androidx.core.content.FileProvider"

13
app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt

@ -12,6 +12,7 @@ import android.widget.TextView
import androidx.annotation.StringRes
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.CustomURLSpan
import com.keylesspalace.tusky.util.hide
import kotlinx.android.synthetic.main.activity_about.*
import kotlinx.android.synthetic.main.toolbar_basic.*
import net.accelf.yuito.AccessTokenLoginActivity
@ -34,7 +35,11 @@ class AboutActivity : BottomSheetActivity(), Injectable {
onEasterEggExecute()
}
versionTextView.text = getString(R.string.about_tusky_version, BuildConfig.VERSION_NAME)
versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
if(BuildConfig.CUSTOM_INSTANCE.isBlank()) {
aboutPoweredByTusky.hide()
}
aboutLicenseInfoTextView.setClickableTextWithoutUnderlines(R.string.about_tusky_license)
aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site)
@ -42,7 +47,7 @@ class AboutActivity : BottomSheetActivity(), Injectable {
aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
tuskyProfileButton.setOnClickListener {
onAccountButtonClick()
viewUrl(BuildConfig.SUPPORT_ACCOUNT_URL, BuildConfig.SUPPORT_ACCOUNT_URL)
}
aboutLicensesButton.setOnClickListener {
@ -55,10 +60,6 @@ class AboutActivity : BottomSheetActivity(), Injectable {
startActivityWithSlideInAnimation(Intent(this, AccessTokenLoginActivity::class.java))
}
private fun onAccountButtonClick() {
viewUrl("https://odakyu.app/@ars42525", getString(R.string.about_tusky_account))
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {

67
app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt

@ -18,10 +18,10 @@ package com.keylesspalace.tusky
import android.animation.ArgbEvaluator
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -35,13 +35,18 @@ import androidx.core.content.ContextCompat
import androidx.emoji.text.EmojiCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.keylesspalace.tusky.adapter.AccountFieldAdapter
import com.keylesspalace.tusky.components.report.ReportActivity
import com.keylesspalace.tusky.di.ViewModelFactory
@ -86,8 +91,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
@ColorInt
private var toolbarColor: Int = 0
@ColorInt
private var backgroundColor: Int = 0
@ColorInt
private var statusBarColorTransparent: Int = 0
@ColorInt
private var statusBarColorOpaque: Int = 0
@ -107,6 +110,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadResources()
makeNotificationBarTransparent()
setContentView(R.layout.activity_account)
@ -119,7 +123,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false)
hideFab = sharedPrefs.getBoolean("fabHide", false)
loadResources()
setupToolbar()
setupTabs()
setupAccountViews()
@ -135,8 +138,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
* Load colors and dimensions from resources
*/
private fun loadResources() {
toolbarColor = ThemeUtils.getColor(this, R.attr.toolbar_background_color)
backgroundColor = ThemeUtils.getColor(this, android.R.attr.colorBackground)
toolbarColor = ThemeUtils.getColor(this, R.attr.colorSurface)
statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter)
statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark)
avatarSize = resources.getDimension(R.dimen.account_activity_avatar_size)
@ -187,16 +189,21 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
*/
private fun setupTabs() {
// Setup the tabs and timeline pager.
adapter = AccountPagerAdapter(supportFragmentManager, viewModel.accountId)
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
adapter.setPageTitles(pageTitles)
accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
R.drawable.tab_page_margin_dark)
accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable)
adapter = AccountPagerAdapter(this, viewModel.accountId)
accountFragmentViewPager.adapter = adapter
accountFragmentViewPager.offscreenPageLimit = 2
accountTabLayout.setupWithViewPager(accountFragmentViewPager)
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
TabLayoutMediator(accountTabLayout, accountFragmentViewPager) {
tab, position ->
tab.text = pageTitles[position]
}.attach()
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
accountFragmentViewPager.setPageTransformer(MarginPageTransformer(pageMargin))
accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
tab?.position?.let { position ->
@ -211,9 +218,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
})
}
/**
* Setup toolbar
*/
private fun setupToolbar() {
// set toolbar top margin according to system window insets
accountCoordinatorLayout.setOnApplyWindowInsetsListener { _, insets ->
@ -236,6 +240,23 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
val appBarElevation = resources.getDimension(R.dimen.actionbar_elevation)
val toolbarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
toolbarBackground.fillColor = ColorStateList.valueOf(Color.TRANSPARENT)
accountToolbar.background = toolbarBackground
accountHeaderInfoContainer.background = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation)
val avatarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation).apply {
fillColor = ColorStateList.valueOf(toolbarColor)
elevation = appBarElevation
shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius))
.build()
}
accountAvatarImageView.background = avatarBackground
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
@ -281,16 +302,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
accountAvatarImageView.visible(scaledAvatarSize > 0)
var transparencyPercent = abs(verticalOffset) / titleVisibleHeight.toFloat()
if (transparencyPercent > 1) transparencyPercent = 1f
val transparencyPercent = (abs(verticalOffset) / titleVisibleHeight.toFloat()).coerceAtMost(1f)
window.statusBarColor = argbEvaluator.evaluate(transparencyPercent, statusBarColorTransparent, statusBarColorOpaque) as Int
val evaluatedToolbarColor = argbEvaluator.evaluate(transparencyPercent, Color.TRANSPARENT, toolbarColor) as Int
val evaluatedTabBarColor = argbEvaluator.evaluate(transparencyPercent, backgroundColor, toolbarColor) as Int
accountToolbar.setBackgroundColor(evaluatedToolbarColor)
accountHeaderInfoContainer.setBackgroundColor(evaluatedTabBarColor)
accountTabLayout.setBackgroundColor(evaluatedTabBarColor)
toolbarBackground.fillColor = ColorStateList.valueOf(evaluatedToolbarColor)
swipeToRefreshLayout.isEnabled = verticalOffset == 0
}
})
@ -300,7 +319,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
private fun makeNotificationBarTransparent() {
val decorView = window.decorView
decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
window.statusBarColor = Color.TRANSPARENT
window.statusBarColor = statusBarColorTransparent
}
/**

3
app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt

@ -17,13 +17,13 @@
package com.keylesspalace.tusky
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
@ -35,7 +35,6 @@ import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
import com.keylesspalace.tusky.viewmodel.State
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDisposable
import com.uber.autodispose.autoDispose
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.extensions.LayoutContainer

6
app/src/main/java/com/keylesspalace/tusky/BaseActivity.java

@ -23,7 +23,6 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
@ -34,6 +33,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.google.android.material.snackbar.Snackbar;
import com.keylesspalace.tusky.adapter.AccountSelectionAdapter;
@ -52,8 +52,6 @@ import javax.inject.Inject;
public abstract class BaseActivity extends AppCompatActivity implements Injectable {
@Inject
public ThemeUtils themeUtils;
@Inject
public AccountManager accountManager;
@ -75,8 +73,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
setTheme(R.style.TuskyBlackTheme);
}
themeUtils.setAppNightMode(theme, this);
/* set the taskdescription programmatically, the theme would turn it blue */
String appName = getString(R.string.app_name);
Bitmap appIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);

21
app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt

@ -19,6 +19,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -52,7 +53,7 @@ abstract class BottomSheetActivity : BaseActivity() {
val bottomSheetLayout: LinearLayout = findViewById(R.id.item_status_bottom_sheet)
bottomSheet = BottomSheetBehavior.from(bottomSheetLayout)
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
bottomSheet.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
cancelActiveSearch()
@ -64,7 +65,7 @@ abstract class BottomSheetActivity : BaseActivity() {
}
open fun viewUrl(url: String, text: String) {
open fun viewUrl(url: String, text: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) {
if (forceBrowser.contains(text)) {
openLink(url)
return
@ -95,11 +96,11 @@ abstract class BottomSheetActivity : BaseActivity() {
return@subscribe
}
openLink(url)
performUrlFallbackAction(url, lookupFallbackBehavior)
}, {
if (!getCancelSearchRequested(url)) {
onEndSearch(url)
openLink(url)
performUrlFallbackAction(url, lookupFallbackBehavior)
}
})
@ -120,6 +121,13 @@ abstract class BottomSheetActivity : BaseActivity() {
startActivityWithSlideInAnimation(intent)
}
protected open fun performUrlFallbackAction(url: String, fallbackBehavior: PostLookupFallbackBehavior) {
when (fallbackBehavior) {
PostLookupFallbackBehavior.OPEN_IN_BROWSER -> openLink(url)
PostLookupFallbackBehavior.DISPLAY_ERROR -> Toast.makeText(this, getString(R.string.post_lookup_error_format, url), Toast.LENGTH_SHORT).show()
}
}
@VisibleForTesting
fun onBeginSearch(url: String) {
searchUrl = url
@ -201,3 +209,8 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
path.matches("^[^@]+@[^@]+$".toRegex())
}
enum class PostLookupFallbackBehavior {
OPEN_IN_BROWSER,
DISPLAY_ERROR,
}

89
app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt

@ -26,9 +26,9 @@ import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.AccessToken
import com.keylesspalace.tusky.entity.AppCredentials
@ -48,9 +48,6 @@ class LoginActivity : BaseActivity(), Injectable {
lateinit var mastodonApi: MastodonApi
private lateinit var preferences: SharedPreferences
private var domain: String = ""
private var clientId: String? = null
private var clientSecret: String? = null
private val oauthRedirectUri: String
get() {
@ -64,10 +61,16 @@ class LoginActivity : BaseActivity(), Injectable {
setContentView(R.layout.activity_login)
if (savedInstanceState != null) {
domain = savedInstanceState.getString(DOMAIN)!!
clientId = savedInstanceState.getString(CLIENT_ID)
clientSecret = savedInstanceState.getString(CLIENT_SECRET)
if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
}
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
Glide.with(loginLogo)
.load(BuildConfig.CUSTOM_LOGO_URL)
.placeholder(null)
.into(loginLogo)
}
preferences = getSharedPreferences(
@ -113,13 +116,6 @@ class LoginActivity : BaseActivity(), Injectable {
return super.onOptionsItemSelected(item)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString(DOMAIN, domain)
outState.putString(CLIENT_ID, clientId)
outState.putString(CLIENT_SECRET, clientSecret)
super.onSaveInstanceState(outState)
}
/**
* Obtain the oauth client credentials for this app. This is only necessary the first time the
* app is run on a given server instance. So, after the first authentication, they are
@ -129,7 +125,7 @@ class LoginActivity : BaseActivity(), Injectable {
loginButton.isEnabled = false
domain = canonicalizeDomain(domainEditText.text.toString())
val domain = canonicalizeDomain(domainEditText.text.toString())
try {
HttpUrl.Builder().host(domain).scheme("https").build()
@ -150,10 +146,16 @@ class LoginActivity : BaseActivity(), Injectable {
return
}
val credentials = response.body()
clientId = credentials!!.clientId
clientSecret = credentials.clientSecret
val clientId = credentials!!.clientId
val clientSecret = credentials.clientSecret
redirectUserToAuthorizeAndLogin(domainEditText)
preferences.edit()
.putString("domain", domain)
.putString("clientId", clientId)
.putString("clientSecret", clientSecret)
.apply()
redirectUserToAuthorizeAndLogin(domain, clientId)
}
override fun onFailure(call: Call<AppCredentials>, t: Throwable) {
@ -166,22 +168,22 @@ class LoginActivity : BaseActivity(), Injectable {
mastodonApi
.authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri,
OAUTH_SCOPES, getString(R.string.app_website))
OAUTH_SCOPES, getString(R.string.tusky_website))
.enqueue(callback)
setLoading(true)
}
private fun redirectUserToAuthorizeAndLogin(editText: EditText) {
private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) {
/* To authorize this app and log in it's necessary to redirect to the domain given,
* activity_login there, and the server will redirect back to the app with its response. */
* login there, and the server will redirect back to the app with its response. */
val endpoint = MastodonApi.ENDPOINT_AUTHORIZE
val redirectUri = oauthRedirectUri
val parameters = HashMap<String, String>()
parameters["client_id"] = clientId!!
parameters["redirect_uri"] = redirectUri
parameters["response_type"] = "code"
parameters["scope"] = OAUTH_SCOPES
val parameters = mapOf(
"client_id" to clientId,
"redirect_uri" to oauthRedirectUri,
"response_type" to "code",
"scope" to OAUTH_SCOPES
)
val url = "https://" + domain + endpoint + "?" + toQueryString(parameters)
val uri = Uri.parse(url)
if (!openInCustomTab(uri, this)) {
@ -189,21 +191,12 @@ class LoginActivity : BaseActivity(), Injectable {
if (viewIntent.resolveActivity(packageManager) != null) {
startActivity(viewIntent)
} else {
editText.error = getString(R.string.error_no_web_browser_found)
domainEditText.error = getString(R.string.error_no_web_browser_found)
setLoading(false)
}
}
}
override fun onStop() {
super.onStop()
preferences.edit()
.putString("domain", domain)
.putString("clientId", clientId)
.putString("clientSecret", clientSecret)
.apply()
}
override fun onStart() {
super.onStart()
/* Check if we are resuming during authorization by seeing if the intent contains the
@ -216,14 +209,12 @@ class LoginActivity : BaseActivity(), Injectable {
val code = uri.getQueryParameter("code")
val error = uri.getQueryParameter("error")
/* During the redirect roundtrip this Activity usually dies, which wipes out the
* instance variables, so they have to be recovered from where they were saved in
* SharedPreferences. */
domain = preferences.getNonNullString(DOMAIN, "")
clientId = preferences.getString(CLIENT_ID, null)
clientSecret = preferences.getString(CLIENT_SECRET, null)
/* restore variables from SharedPreferences */
val domain = preferences.getNonNullString(DOMAIN, "")
val clientId = preferences.getNonNullString(CLIENT_ID, "")
val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "")
if (code != null && domain.isNotEmpty() && !clientId.isNullOrEmpty() && !clientSecret.isNullOrEmpty()) {
if (code != null && domain.isNotEmpty() && clientId.isNotEmpty() && clientSecret.isNotEmpty()) {
setLoading(true)
/* Since authorization has succeeded, the final step to log in is to exchange
@ -231,7 +222,7 @@ class LoginActivity : BaseActivity(), Injectable {
val callback = object : Callback<AccessToken> {
override fun onResponse(call: Call<AccessToken>, response: Response<AccessToken>) {
if (response.isSuccessful) {
onLoginSuccess(response.body()!!.accessToken)
onLoginSuccess(response.body()!!.accessToken, domain)
} else {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
@ -250,7 +241,7 @@ class LoginActivity : BaseActivity(), Injectable {
}
}
mastodonApi.fetchOAuthToken(domain, clientId!!, clientSecret!!, redirectUri, code,
mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code,
"authorization_code").enqueue(callback)
} else if (error != null) {
/* Authorization failed. Put the error response where the user can read it and they
@ -286,7 +277,7 @@ class LoginActivity : BaseActivity(), Injectable {
return intent.getBooleanExtra(LOGIN_MODE, false)
}
private fun onLoginSuccess(accessToken: String) {
private fun onLoginSuccess(accessToken: String, domain: String) {
setLoading(true)
@ -351,7 +342,7 @@ class LoginActivity : BaseActivity(), Injectable {
.setToolbarColor(toolbarColor)
.build()
try {
customTabsIntent.launchUrl(context, uri)
customTabsIntent.launchUrl(context, uri)
} catch (e: ActivityNotFoundException) {
Log.w(TAG, "Activity was not found for intent $customTabsIntent")
return false

44
app/src/main/java/com/keylesspalace/tusky/MainActivity.java

@ -22,12 +22,10 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@ -35,14 +33,18 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager.widget.ViewPager;
import androidx.preference.PreferenceManager;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.keylesspalace.tusky.appstore.CacheUpdater;
import com.keylesspalace.tusky.appstore.DrawerFooterClickedEvent;
import com.keylesspalace.tusky.appstore.EventHub;
@ -58,7 +60,7 @@ import com.keylesspalace.tusky.interfaces.ReselectableFragment;
import com.keylesspalace.tusky.pager.MainPagerAdapter;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NotificationHelper;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.util.ShareShortcutHelper;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
@ -121,7 +123,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private AccountHeader headerResult;
private Drawer drawer;
private TabLayout tabLayout;
private ViewPager viewPager;
private ViewPager2 viewPager;
private SharedPreferences defPrefs;
private int notificationTabPosition;
@ -149,7 +151,18 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
boolean showNotificationTab = false;
if (intent != null) {
/** there are two possibilities the accountId can be passed to MainActivity:
- from our code as long 'account_id'
- from share shortcuts as String 'android.intent.extra.shortcut.ID'
*/
long accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1);
if(accountId == -1) {
String accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID);
if(accountIdString != null) {
accountId = Long.parseLong(accountIdString);
}
}
boolean accountRequested = (accountId != -1);
if (accountRequested) {
@ -187,7 +200,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
setContentView(R.layout.activity_main);
composeButton = findViewById(R.id.floating_btn);
ImageButton drawerToggle = findViewById(R.id.drawer_toggle);
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.pager);
@ -199,10 +211,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
setupDrawer();
// Setup the navigation drawer toggle button.
ThemeUtils.setDrawableTint(this, drawerToggle.getDrawable(), R.attr.toolbar_icon_tint);
drawerToggle.setOnClickListener(v -> drawer.openDrawer());
/* Fetch user info while we're doing other things. This has to be done after setting up the
* drawer, though, because its callback touches the header in the drawer. */
fetchUserInfo();
@ -212,10 +220,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
defPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
viewPager.setPageMargin(pageMargin);
Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
R.drawable.tab_page_margin_dark);
viewPager.setPageMarginDrawable(pageMarginDrawable);
viewPager.setPageTransformer(new MarginPageTransformer(pageMargin));
if (defPrefs.getBoolean("viewPagerOffScreenLimit", false)) {
viewPager.setOffscreenPageLimit(9);
}
@ -349,7 +354,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
if (intent != null) {
String statusUrl = intent.getStringExtra(STATUS_URL);
if (statusUrl != null) {
viewUrl(statusUrl, statusUrl);
viewUrl(statusUrl, statusUrl, PostLookupFallbackBehavior.DISPLAY_ERROR);
}
}
}
@ -439,6 +444,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
.withHasStableIds(true)
.withSelectedItem(-1)
.withDrawerItems(listItems)
.withToolbar(findViewById(R.id.main_toolbar))
.withOnDrawerItemClickListener((view, position, drawerItem) -> {
if (drawerItem != null) {
long drawerItemIdentifier = drawerItem.getIdentifier();
@ -534,10 +540,11 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private void setupTabs(boolean selectNotificationTab) {
List<TabData> tabs = accountManager.getActiveAccount().getTabPreferences();
adapter = new MainPagerAdapter(tabs, getSupportFragmentManager());
adapter = new MainPagerAdapter(tabs, this);
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { }).attach();
tabLayout.removeAllTabs();
for (int i = 0; i < tabs.size(); i++) {
TabLayout.Tab tab = tabLayout.newTab()
@ -610,6 +617,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
NotificationHelper.deleteNotificationChannelsForAccount(accountManager.getActiveAccount(), MainActivity.this);
cacheUpdater.clearForUser(activeAccount.getId());
conversationRepository.deleteCacheForAccount(activeAccount.getId());
ShareShortcutHelper.removeShortcut(this, activeAccount);
AccountEntity newAccount = accountManager.logActiveAccountOut();
@ -667,6 +675,8 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
updateProfiles();
ShareShortcutHelper.updateShortcut(this, accountManager.getActiveAccount());
}
private void updateProfiles() {

12
app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt

@ -19,11 +19,10 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import android.util.Log
import android.view.MenuItem
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.fragment.preference.*
@ -123,18 +122,11 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
"appTheme" -> {
val theme = sharedPreferences.getNonNullString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
Log.d("activeTheme", theme)
themeUtils.setAppNightMode(theme, this)
ThemeUtils.setAppNightMode(theme)
restartActivitiesOnExit = true
this.restartCurrentActivity()
// MODE_NIGHT_FOLLOW_SYSTEM workaround part 2 :/
when(theme){
ThemeUtils.THEME_SYSTEM -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
//workaround end
}
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "viewPagerOffScreenLimit" -> {
restartActivitiesOnExit = true

11
app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java

@ -17,10 +17,11 @@ package com.keylesspalace.tusky;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import androidx.emoji.text.EmojiCompat;
import androidx.preference.PreferenceManager;
import androidx.room.Room;
import com.evernote.android.job.JobManager;
@ -30,6 +31,7 @@ import com.keylesspalace.tusky.di.AppInjector;
import com.keylesspalace.tusky.util.EmojiCompatFont;
import com.keylesspalace.tusky.util.LocaleManager;
import com.keylesspalace.tusky.util.NotificationPullJobCreator;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.uber.autodispose.AutoDisposePlugins;
import org.conscrypt.Conscrypt;
@ -91,6 +93,7 @@ public class TuskyApplication extends Application implements HasAndroidInjector
initAppInjector();
initEmojiCompat();
initNightMode();
JobManager.create(this).addJobCreator(notificationPullJobCreator);
@ -133,6 +136,12 @@ public class TuskyApplication extends Application implements HasAndroidInjector
AppInjector.INSTANCE.init(this);
}
protected void initNightMode() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT);
ThemeUtils.setAppNightMode(theme);
}
public ServiceLocator getServiceLocator() {
return serviceLocator;
}

34
app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt

@ -37,9 +37,10 @@ import android.view.View
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.bumptech.glide.request.FutureTarget
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
@ -110,23 +111,23 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
// Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
// but it cannot be expressed and if I don't specify type explicitly compilation fails
// (probably a bug in compiler)
val adapter: PagerAdapter = if (attachments != null) {
val adapter: ViewMediaAdapter = if (attachments != null) {
val realAttachs = attachments!!.map(AttachmentViewData::attachment)
// Setup the view pager.
ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition)
ImagePagerAdapter(this, realAttachs, initialPosition)
} else {
val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL)
?: throw IllegalArgumentException("attachment list or avatar url has to be set")
AvatarImagePagerAdapter(supportFragmentManager, avatarUrl)
AvatarImagePagerAdapter(this, avatarUrl)
}
viewPager.adapter = adapter
viewPager.currentItem = initialPosition
viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
viewPager.setCurrentItem(initialPosition, false)
viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
toolbar.title = adapter.getPageTitle(position)
toolbar.title = getPageTitle(position)
}
})
@ -136,7 +137,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setDisplayShowHomeEnabled(true)
actionBar.title = adapter.getPageTitle(initialPosition)
actionBar.title = getPageTitle(initialPosition)
}
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
toolbar.setOnMenuItemClickListener { item: MenuItem ->
@ -153,7 +154,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
window.statusBarColor = Color.BLACK
window.sharedElementEnterTransition.addListener(object : NoopTransitionListener {
override fun onTransitionEnd(transition: Transition) {
(adapter as SharedElementTransitionListener).onTransitionEnd()
adapter.onTransitionEnd(viewPager.currentItem)
window.sharedElementEnterTransition.removeListener(this)
}
})
@ -198,6 +199,13 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
.start()
}
private fun getPageTitle(position: Int): CharSequence {
if(attachments == null) {
return ""
}
return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size)
}
private fun downloadMedia() {
val url = attachments!![viewPager.currentItem].attachment.url
val filename = Uri.parse(url).lastPathSegment
@ -227,7 +235,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
private fun copyLink() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url)
clipboard.setPrimaryClip(ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url))
}
private fun shareMedia() {
@ -323,8 +331,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
}
}
interface SharedElementTransitionListener {
fun onTransitionEnd()
abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
abstract fun onTransitionEnd(position: Int)
}
interface NoopTransitionListener : Transition.TransitionListener {

3
app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt

@ -16,16 +16,15 @@
package com.keylesspalace.tusky.adapter
import android.content.Context
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.loadAvatar
import kotlinx.android.synthetic.main.item_autocomplete_account.view.*
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {

6
app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java

@ -1,13 +1,13 @@
package com.keylesspalace.tusky.adapter;
import androidx.recyclerview.widget.RecyclerView;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;

8
app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java

@ -15,10 +15,6 @@
package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -26,6 +22,10 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;

8
app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java

@ -15,10 +15,6 @@
package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -26,6 +22,10 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;

8
app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java

@ -1,9 +1,5 @@
package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -11,6 +7,10 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;