Merge remote-tracking branch 'tuskyapp/develop'

This commit is contained in:
kyori19 2019-11-19 21:52:13 +09:00
commit 762b2225ca
173 changed files with 1801 additions and 1371 deletions

View File

@ -5,7 +5,7 @@
Yuito is fork of [Tusky](https://github.com/tuskyapp/Tusky). 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 ## 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)! 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 ### Head of development
This app was developed by [@ars42525@odakyu.app](https://odakyu.app/@ars42525). This app was developed by [@ars42525@odakyu.app](https://odakyu.app/@ars42525).

View File

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

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -12,6 +12,7 @@ import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.CustomURLSpan import com.keylesspalace.tusky.util.CustomURLSpan
import com.keylesspalace.tusky.util.hide
import kotlinx.android.synthetic.main.activity_about.* import kotlinx.android.synthetic.main.activity_about.*
import kotlinx.android.synthetic.main.toolbar_basic.* import kotlinx.android.synthetic.main.toolbar_basic.*
import net.accelf.yuito.AccessTokenLoginActivity import net.accelf.yuito.AccessTokenLoginActivity
@ -34,7 +35,11 @@ class AboutActivity : BottomSheetActivity(), Injectable {
onEasterEggExecute() 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) aboutLicenseInfoTextView.setClickableTextWithoutUnderlines(R.string.about_tusky_license)
aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site) aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site)
@ -42,7 +47,7 @@ class AboutActivity : BottomSheetActivity(), Injectable {
aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site) aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
tuskyProfileButton.setOnClickListener { tuskyProfileButton.setOnClickListener {
onAccountButtonClick() viewUrl(BuildConfig.SUPPORT_ACCOUNT_URL, BuildConfig.SUPPORT_ACCOUNT_URL)
} }
aboutLicensesButton.setOnClickListener { aboutLicensesButton.setOnClickListener {
@ -55,10 +60,6 @@ class AboutActivity : BottomSheetActivity(), Injectable {
startActivityWithSlideInAnimation(Intent(this, AccessTokenLoginActivity::class.java)) 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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ 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 androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import com.google.android.material.bottomsheet.BottomSheetBehavior 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) val bottomSheetLayout: LinearLayout = findViewById(R.id.item_status_bottom_sheet)
bottomSheet = BottomSheetBehavior.from(bottomSheetLayout) bottomSheet = BottomSheetBehavior.from(bottomSheetLayout)
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { bottomSheet.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) { if (newState == BottomSheetBehavior.STATE_HIDDEN) {
cancelActiveSearch() 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)) { if (forceBrowser.contains(text)) {
openLink(url) openLink(url)
return return
@ -95,11 +96,11 @@ abstract class BottomSheetActivity : BaseActivity() {
return@subscribe return@subscribe
} }
openLink(url) performUrlFallbackAction(url, lookupFallbackBehavior)
}, { }, {
if (!getCancelSearchRequested(url)) { if (!getCancelSearchRequested(url)) {
onEndSearch(url) onEndSearch(url)
openLink(url) performUrlFallbackAction(url, lookupFallbackBehavior)
} }
}) })
@ -120,6 +121,13 @@ abstract class BottomSheetActivity : BaseActivity() {
startActivityWithSlideInAnimation(intent) 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 @VisibleForTesting
fun onBeginSearch(url: String) { fun onBeginSearch(url: String) {
searchUrl = url searchUrl = url
@ -201,3 +209,8 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
path.matches("^[^@]+@[^@]+$".toRegex()) path.matches("^[^@]+@[^@]+$".toRegex())
} }
enum class PostLookupFallbackBehavior {
OPEN_IN_BROWSER,
DISPLAY_ERROR,
}

View File

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

View File

@ -22,12 +22,10 @@ import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -35,14 +33,18 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.emoji.text.EmojiCompat; import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle; 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.bumptech.glide.Glide;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout; 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.CacheUpdater;
import com.keylesspalace.tusky.appstore.DrawerFooterClickedEvent; import com.keylesspalace.tusky.appstore.DrawerFooterClickedEvent;
import com.keylesspalace.tusky.appstore.EventHub; 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.pager.MainPagerAdapter;
import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NotificationHelper; 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.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.materialdrawer.AccountHeader; import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.AccountHeaderBuilder;
@ -121,7 +123,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private AccountHeader headerResult; private AccountHeader headerResult;
private Drawer drawer; private Drawer drawer;
private TabLayout tabLayout; private TabLayout tabLayout;
private ViewPager viewPager; private ViewPager2 viewPager;
private SharedPreferences defPrefs; private SharedPreferences defPrefs;
private int notificationTabPosition; private int notificationTabPosition;
@ -149,7 +151,18 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
boolean showNotificationTab = false; boolean showNotificationTab = false;
if (intent != null) { 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); 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); boolean accountRequested = (accountId != -1);
if (accountRequested) { if (accountRequested) {
@ -187,7 +200,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
composeButton = findViewById(R.id.floating_btn); composeButton = findViewById(R.id.floating_btn);
ImageButton drawerToggle = findViewById(R.id.drawer_toggle);
tabLayout = findViewById(R.id.tab_layout); tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.pager); viewPager = findViewById(R.id.pager);
@ -199,10 +211,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
setupDrawer(); 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 /* 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. */ * drawer, though, because its callback touches the header in the drawer. */
fetchUserInfo(); fetchUserInfo();
@ -212,10 +220,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
defPrefs = PreferenceManager.getDefaultSharedPreferences(this); defPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin); int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
viewPager.setPageMargin(pageMargin); viewPager.setPageTransformer(new MarginPageTransformer(pageMargin));
Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
R.drawable.tab_page_margin_dark);
viewPager.setPageMarginDrawable(pageMarginDrawable);
if (defPrefs.getBoolean("viewPagerOffScreenLimit", false)) { if (defPrefs.getBoolean("viewPagerOffScreenLimit", false)) {
viewPager.setOffscreenPageLimit(9); viewPager.setOffscreenPageLimit(9);
} }
@ -349,7 +354,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
if (intent != null) { if (intent != null) {
String statusUrl = intent.getStringExtra(STATUS_URL); String statusUrl = intent.getStringExtra(STATUS_URL);
if (statusUrl != null) { 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) .withHasStableIds(true)
.withSelectedItem(-1) .withSelectedItem(-1)
.withDrawerItems(listItems) .withDrawerItems(listItems)
.withToolbar(findViewById(R.id.main_toolbar))
.withOnDrawerItemClickListener((view, position, drawerItem) -> { .withOnDrawerItemClickListener((view, position, drawerItem) -> {
if (drawerItem != null) { if (drawerItem != null) {
long drawerItemIdentifier = drawerItem.getIdentifier(); long drawerItemIdentifier = drawerItem.getIdentifier();
@ -534,10 +540,11 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private void setupTabs(boolean selectNotificationTab) { private void setupTabs(boolean selectNotificationTab) {
List<TabData> tabs = accountManager.getActiveAccount().getTabPreferences(); List<TabData> tabs = accountManager.getActiveAccount().getTabPreferences();
adapter = new MainPagerAdapter(tabs, getSupportFragmentManager()); adapter = new MainPagerAdapter(tabs, this);
viewPager.setAdapter(adapter); viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager); new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { }).attach();
tabLayout.removeAllTabs(); tabLayout.removeAllTabs();
for (int i = 0; i < tabs.size(); i++) { for (int i = 0; i < tabs.size(); i++) {
TabLayout.Tab tab = tabLayout.newTab() TabLayout.Tab tab = tabLayout.newTab()
@ -610,6 +617,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
NotificationHelper.deleteNotificationChannelsForAccount(accountManager.getActiveAccount(), MainActivity.this); NotificationHelper.deleteNotificationChannelsForAccount(accountManager.getActiveAccount(), MainActivity.this);
cacheUpdater.clearForUser(activeAccount.getId()); cacheUpdater.clearForUser(activeAccount.getId());
conversationRepository.deleteCacheForAccount(activeAccount.getId()); conversationRepository.deleteCacheForAccount(activeAccount.getId());
ShareShortcutHelper.removeShortcut(this, activeAccount);
AccountEntity newAccount = accountManager.logActiveAccountOut(); AccountEntity newAccount = accountManager.logActiveAccountOut();
@ -667,6 +675,8 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
updateProfiles(); updateProfiles();
ShareShortcutHelper.updateShortcut(this, accountManager.getActiveAccount());
} }
private void updateProfiles() { private void updateProfiles() {

View File

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

View File

@ -17,10 +17,11 @@ package com.keylesspalace.tusky;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.preference.PreferenceManager;
import androidx.emoji.text.EmojiCompat; import androidx.emoji.text.EmojiCompat;
import androidx.preference.PreferenceManager;
import androidx.room.Room; import androidx.room.Room;
import com.evernote.android.job.JobManager; 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.EmojiCompatFont;
import com.keylesspalace.tusky.util.LocaleManager; import com.keylesspalace.tusky.util.LocaleManager;
import com.keylesspalace.tusky.util.NotificationPullJobCreator; import com.keylesspalace.tusky.util.NotificationPullJobCreator;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.uber.autodispose.AutoDisposePlugins; import com.uber.autodispose.AutoDisposePlugins;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
@ -91,6 +93,7 @@ public class TuskyApplication extends Application implements HasAndroidInjector
initAppInjector(); initAppInjector();
initEmojiCompat(); initEmojiCompat();
initNightMode();
JobManager.create(this).addJobCreator(notificationPullJobCreator); JobManager.create(this).addJobCreator(notificationPullJobCreator);
@ -133,6 +136,12 @@ public class TuskyApplication extends Application implements HasAndroidInjector
AppInjector.INSTANCE.init(this); 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() { public ServiceLocator getServiceLocator() {
return serviceLocator; return serviceLocator;
} }

View File

@ -37,9 +37,10 @@ import android.view.View
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.viewpager.widget.PagerAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.FutureTarget import com.bumptech.glide.request.FutureTarget
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
@ -110,23 +111,23 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
// 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
// (probably a bug in compiler) // (probably a bug in compiler)
val adapter: PagerAdapter = if (attachments != null) { val adapter: ViewMediaAdapter = if (attachments != null) {
val realAttachs = attachments!!.map(AttachmentViewData::attachment) val realAttachs = attachments!!.map(AttachmentViewData::attachment)
// Setup the view pager. // Setup the view pager.
ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition) ImagePagerAdapter(this, realAttachs, initialPosition)
} else { } else {
val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL) val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL)
?: throw IllegalArgumentException("attachment list or avatar url has to be set") ?: throw IllegalArgumentException("attachment list or avatar url has to be set")
AvatarImagePagerAdapter(supportFragmentManager, avatarUrl) AvatarImagePagerAdapter(this, avatarUrl)
} }
viewPager.adapter = adapter viewPager.adapter = adapter
viewPager.currentItem = initialPosition viewPager.setCurrentItem(initialPosition, false)
viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { 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) { if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setDisplayShowHomeEnabled(true) actionBar.setDisplayShowHomeEnabled(true)
actionBar.title = adapter.getPageTitle(initialPosition) actionBar.title = getPageTitle(initialPosition)
} }
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
toolbar.setOnMenuItemClickListener { item: MenuItem -> toolbar.setOnMenuItemClickListener { item: MenuItem ->
@ -153,7 +154,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
window.statusBarColor = Color.BLACK window.statusBarColor = Color.BLACK
window.sharedElementEnterTransition.addListener(object : NoopTransitionListener { window.sharedElementEnterTransition.addListener(object : NoopTransitionListener {
override fun onTransitionEnd(transition: Transition) { override fun onTransitionEnd(transition: Transition) {
(adapter as SharedElementTransitionListener).onTransitionEnd() adapter.onTransitionEnd(viewPager.currentItem)
window.sharedElementEnterTransition.removeListener(this) window.sharedElementEnterTransition.removeListener(this)
} }
}) })
@ -198,6 +199,13 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
.start() .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() { private fun downloadMedia() {
val url = attachments!![viewPager.currentItem].attachment.url val url = attachments!![viewPager.currentItem].attachment.url
val filename = Uri.parse(url).lastPathSegment val filename = Uri.parse(url).lastPathSegment
@ -227,7 +235,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
private fun copyLink() { private fun copyLink() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 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() { private fun shareMedia() {
@ -323,8 +331,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
} }
} }
interface SharedElementTransitionListener { abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
fun onTransitionEnd() abstract fun onTransitionEnd(position: Int)
} }
interface NoopTransitionListener : Transition.TransitionListener { interface NoopTransitionListener : Transition.TransitionListener {

View File

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

View File

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

View File

@ -15,10 +15,6 @@
package com.keylesspalace.tusky.adapter; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -26,6 +22,10 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;

View File

@ -15,10 +15,6 @@
package com.keylesspalace.tusky.adapter; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -26,6 +22,10 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;

View File

@ -1,9 +1,5 @@
package com.keylesspalace.tusky.adapter; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -11,6 +7,10 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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.R;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;

View File

@ -29,6 +29,7 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.HtmlUtils import com.keylesspalace.tusky.util.HtmlUtils
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.PollOptionViewData import com.keylesspalace.tusky.viewdata.PollOptionViewData
import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent import com.keylesspalace.tusky.viewdata.calculatePercent
class PollAdapter: RecyclerView.Adapter<PollViewHolder>() { class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
@ -71,10 +72,7 @@ class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
when(mode) { when(mode) {
RESULT -> { RESULT -> {
val percent = calculatePercent(option.votesCount, voteCount) val percent = calculatePercent(option.votesCount, voteCount)
val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(buildDescription(option.title, percent, holder.resultTextView.context), emojis, holder.resultTextView)
val pollOptionText = holder.resultTextView.context.getString(R.string.poll_option_format, percent, option.title)
val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, holder.resultTextView)
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100 val level = percent * 100

View File

@ -57,6 +57,8 @@ import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.SparkEventListener; import at.connyduck.sparkbutton.SparkEventListener;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
public static class Key { public static class Key {
public static final String KEY_CREATED = "created"; public static final String KEY_CREATED = "created";
@ -390,7 +392,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else { } else {
inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(), inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(),
R.attr.status_reblog_inactive_drawable, R.drawable.reblog_inactive_dark); R.attr.status_reblog_inactive_drawable, R.drawable.reblog_inactive_dark);
activeId = R.drawable.reblog_active; activeId = R.drawable.ic_reblog_active_24dp;
} }
reblogButton.setInactiveImage(inactiveId); reblogButton.setInactiveImage(inactiveId);
reblogButton.setActiveImage(activeId); reblogButton.setActiveImage(activeId);
@ -905,10 +907,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (i < options.size()) { if (i < options.size()) {
int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotesCount()); int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotesCount());
args[i] = HtmlUtils.fromHtml(context.getString( args[i] = buildDescription(options.get(i).getTitle(), percent, context);
R.string.poll_option_format,
percent,
options.get(i).getTitle()));
} else { } else {
args[i] = ""; args[i] = "";
} }

View File

@ -16,7 +16,6 @@
package com.keylesspalace.tusky.components.conversation; package com.keylesspalace.tusky.components.conversation;
import android.content.Context; import android.content.Context;
import android.preference.PreferenceManager;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -24,6 +23,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;

View File

@ -17,13 +17,13 @@ package com.keylesspalace.tusky.components.conversation
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator

View File

@ -59,6 +59,7 @@ class ConversationsViewModel @Inject constructor(
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) } .doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) }
.onErrorReturnItem(0)
.subscribe() .subscribe()
.addTo(disposables) .addTo(disposables)
} }
@ -77,6 +78,7 @@ class ConversationsViewModel @Inject constructor(
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) } .doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) }
.onErrorReturnItem(0)
.subscribe() .subscribe()
.addTo(disposables) .addTo(disposables)
} }

View File

@ -16,7 +16,6 @@
package com.keylesspalace.tusky.components.report.fragments package com.keylesspalace.tusky.components.report.fragments
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -26,6 +25,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator

View File

@ -23,6 +23,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.google.android.material.tabs.TabLayoutMediator
import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter
@ -57,9 +58,13 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha
} }
private fun setupPages() { private fun setupPages() {
pages.adapter = SearchPagerAdapter(this, supportFragmentManager) pages.adapter = SearchPagerAdapter(this)
tabs.setupWithViewPager(pages)
pages.offscreenPageLimit = 4 pages.offscreenPageLimit = 4
TabLayoutMediator(tabs, pages) {
tab, position ->
tab.text = getPageTitle(position)
}.attach()
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -75,9 +80,7 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha
.actionView as SearchView .actionView as SearchView
setupSearchView(searchView) setupSearchView(searchView)
if (viewModel.currentQuery != null) { searchView.setQuery(viewModel.currentQuery, false)
searchView.setQuery(viewModel.currentQuery, false)
}
return true return true
} }
@ -100,9 +103,19 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha
return false return false
} }
private fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> getString(R.string.title_statuses)
1 -> getString(R.string.title_accounts)
2 -> getString(R.string.title_hashtags_dialog)
3 -> getString(R.string.title_notestock)
else -> throw IllegalArgumentException("Unknown page index: $position")
}
}
private fun handleIntent(intent: Intent) { private fun handleIntent(intent: Intent) {
if (Intent.ACTION_SEARCH == intent.action) { if (Intent.ACTION_SEARCH == intent.action) {
viewModel.currentQuery = intent.getStringExtra(SearchManager.QUERY) viewModel.currentQuery = intent.getStringExtra(SearchManager.QUERY) ?: ""
viewModel.search(viewModel.currentQuery) viewModel.search(viewModel.currentQuery)
} }
} }

View File

@ -21,6 +21,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import javax.inject.Inject import javax.inject.Inject
class SearchViewModel @Inject constructor( class SearchViewModel @Inject constructor(
@ -29,7 +30,7 @@ class SearchViewModel @Inject constructor(
private val timelineCases: TimelineCases, private val timelineCases: TimelineCases,
private val accountManager: AccountManager) : ViewModel() { private val accountManager: AccountManager) : ViewModel() {
var currentQuery: String? = null var currentQuery: String = ""
var activeAccount: AccountEntity? var activeAccount: AccountEntity?
get() = accountManager.activeAccount get() = accountManager.activeAccount
@ -72,7 +73,7 @@ class SearchViewModel @Inject constructor(
private val loadedStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>() private val loadedStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>()
private val loadedNotestockStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>() private val loadedNotestockStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>()
fun search(query: String?) { fun search(query: String) {
loadedStatuses.clear() loadedStatuses.clear()
repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) { repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) {
(it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia, alwaysOpenSpoiler)!!) } (it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia, alwaysOpenSpoiler)!!) }
@ -84,7 +85,7 @@ class SearchViewModel @Inject constructor(
repoResultAccount.value = accountsRepository.getSearchData(SearchType.Account, query, disposables) { repoResultAccount.value = accountsRepository.getSearchData(SearchType.Account, query, disposables) {
it?.accounts ?: emptyList() it?.accounts ?: emptyList()
} }
val hashtagQuery = if (query != null && query.startsWith("#")) query else "#$query" val hashtagQuery = if (query.startsWith("#")) query else "#$query"
repoResultHashTag.value = repoResultHashTag.value =
hashtagsRepository.getSearchData(SearchType.Hashtag, hashtagQuery, disposables) { hashtagsRepository.getSearchData(SearchType.Hashtag, hashtagQuery, disposables) {
it?.hashtags ?: emptyList() it?.hashtags ?: emptyList()
@ -107,9 +108,14 @@ class SearchViewModel @Inject constructor(
fun removeItem(status: Pair<Status, StatusViewData.Concrete>) { fun removeItem(status: Pair<Status, StatusViewData.Concrete>) {
timelineCases.delete(status.first.id) timelineCases.delete(status.first.id)
.subscribe() .subscribe({
if (loadedStatuses.remove(status)) if (loadedStatuses.remove(status))
repoResultStatus.value?.refresh?.invoke() repoResultStatus.value?.refresh?.invoke()
}, {
err -> Log.d(TAG, "Failed to delete status", err)
})
.addTo(disposables)
} }
fun removeNotestockItem(status: Pair<Status, StatusViewData.Concrete>) { fun removeNotestockItem(status: Pair<Status, StatusViewData.Concrete>) {

View File

@ -22,12 +22,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.adapter.AccountViewHolder
import com.keylesspalace.tusky.adapter.StatusViewHolder
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.viewdata.StatusViewData
class SearchAccountsAdapter(private val linkListener: LinkListener) class SearchAccountsAdapter(private val linkListener: LinkListener)
: PagedListAdapter<Account, RecyclerView.ViewHolder>(STATUS_COMPARATOR) { : PagedListAdapter<Account, RecyclerView.ViewHolder>(STATUS_COMPARATOR) {

View File

@ -15,18 +15,17 @@
package com.keylesspalace.tusky.components.search.adapter package com.keylesspalace.tusky.components.search.adapter
import android.content.Context
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentPagerAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment
import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment
import com.keylesspalace.tusky.components.search.fragments.SearchNotestockFragment import com.keylesspalace.tusky.components.search.fragments.SearchNotestockFragment
import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment
class SearchPagerAdapter(private val context: Context, manager: FragmentManager) : FragmentPagerAdapter(manager) { class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItem(position: Int): Fragment {
override fun createFragment(position: Int): Fragment {
return when (position) { return when (position) {
0 -> SearchStatusesFragment.newInstance() 0 -> SearchStatusesFragment.newInstance()
1 -> SearchAccountsFragment.newInstance() 1 -> SearchAccountsFragment.newInstance()
@ -36,15 +35,6 @@ class SearchPagerAdapter(private val context: Context, manager: FragmentManager)
} }
} }
override fun getPageTitle(position: Int): CharSequence? { override fun getItemCount() = 4
return when (position) {
0 -> context.getString(R.string.title_statuses)
1 -> context.getString(R.string.title_accounts)
2 -> context.getString(R.string.title_hashtags_dialog)
3 -> context.getString(R.string.title_notestock)
else -> throw IllegalArgumentException("Unknown page index: $position")
}
}
override fun getCount(): Int = 4
} }

View File

@ -28,7 +28,7 @@ import javax.inject.Inject
abstract class SearchFragment<T> : Fragment(), abstract class SearchFragment<T> : Fragment(),
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener { LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
private var isSwipeToRefreshEnabled: Boolean = true
private var snackbarErrorRetry: Snackbar? = null private var snackbarErrorRetry: Snackbar? = null
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory

View File

@ -24,7 +24,6 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.preference.PreferenceManager
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
@ -36,6 +35,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.paging.PagedListAdapter import androidx.paging.PagedListAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.keylesspalace.tusky.* import com.keylesspalace.tusky.*
@ -305,8 +305,7 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
} }
R.id.status_copy_link -> { R.id.status_copy_link -> {
val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(null, statusUrl) clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl))
clipboard.primaryClip = clip
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
} }
R.id.status_open_as -> { R.id.status_open_as -> {

View File

@ -19,8 +19,8 @@ package com.keylesspalace.tusky.di
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.preference.PreferenceManager
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.TuskyApplication
import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.EventHubImpl import com.keylesspalace.tusky.appstore.EventHubImpl

View File

@ -62,8 +62,7 @@ data class Account(
if (other !is Account) { if (other !is Account) {
return false return false
} }
val account = other as Account? return other.id == this.id
return account?.id == this.id
} }
fun deepEquals(other: Account): Boolean { fun deepEquals(other: Account): Boolean {

View File

@ -81,7 +81,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
private var currentCall: Call<List<Status>>? = null private var currentCall: Call<List<Status>>? = null
private val statuses = mutableListOf<Status>() private val statuses = mutableListOf<Status>()
private var fetchingStatus = FetchingStatus.NOT_FETCHING private var fetchingStatus = FetchingStatus.NOT_FETCHING
private var isVisibleToUser: Boolean = false
private lateinit var accountId: String private lateinit var accountId: String
@ -216,7 +215,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
} }
}) })
if (isVisibleToUser) doInitialLoadingIfNeeded() doInitialLoadingIfNeeded()
} }
private fun refresh() { private fun refresh() {
@ -235,14 +234,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
topProgressBar?.show() topProgressBar?.show()
} }
// That's sort of an optimization to only load media once user has opened the tab
// Attention: can be called before *any* lifecycle method!
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
this.isVisibleToUser = isVisibleToUser
if (isVisibleToUser && isAdded) doInitialLoadingIfNeeded()
}
private fun doInitialLoadingIfNeeded() { private fun doInitialLoadingIfNeeded() {
if (isAdded) { if (isAdded) {
statusView.hide() statusView.hide()

View File

@ -20,7 +20,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -39,6 +38,7 @@ import androidx.arch.core.util.Function;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.AsyncDifferConfig; import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.AsyncListDiffer; import androidx.recyclerview.widget.AsyncListDiffer;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;

View File

@ -45,6 +45,7 @@ import com.keylesspalace.tusky.BottomSheetActivity;
import com.keylesspalace.tusky.ComposeActivity; import com.keylesspalace.tusky.ComposeActivity;
import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.PostLookupFallbackBehavior;
import com.keylesspalace.tusky.ViewMediaActivity; import com.keylesspalace.tusky.ViewMediaActivity;
import com.keylesspalace.tusky.ViewTagActivity; import com.keylesspalace.tusky.ViewTagActivity;
import com.keylesspalace.tusky.components.report.ReportActivity; import com.keylesspalace.tusky.components.report.ReportActivity;
@ -134,7 +135,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
} }
public void onViewUrl(String url, String text) { public void onViewUrl(String url, String text) {
bottomSheetActivity.viewUrl(url, text); bottomSheetActivity.viewUrl(url, text, PostLookupFallbackBehavior.OPEN_IN_BROWSER);
} }
protected void reply(Status status) { protected void reply(Status status) {

View File

@ -22,13 +22,29 @@ import android.content.SharedPreferences;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
import androidx.core.util.Pair;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.AsyncListDiffer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.keylesspalace.tusky.AccountListActivity; import com.keylesspalace.tusky.AccountListActivity;
import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BaseActivity;
@ -86,22 +102,6 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
import androidx.core.util.Pair;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.AsyncListDiffer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import at.connyduck.sparkbutton.helpers.Utils; import at.connyduck.sparkbutton.helpers.Utils;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -377,7 +377,7 @@ public class TimelineFragment extends SFragment implements
Iterator<Either<Placeholder, Status>> iterator = this.statuses.iterator(); Iterator<Either<Placeholder, Status>> iterator = this.statuses.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Either<Placeholder, Status> item = iterator.next(); Either<Placeholder, Status> item = iterator.next();
if(item.isRight()) { if (item.isRight()) {
Status status = item.asRight(); Status status = item.asRight();
if (status.getId().length() < topId.length() || status.getId().compareTo(topId) < 0) { if (status.getId().length() < topId.length() || status.getId().compareTo(topId) < 0) {

View File

@ -18,13 +18,12 @@ package com.keylesspalace.tusky.fragment
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.widget.TextView import android.widget.TextView
import com.keylesspalace.tusky.SharedElementTransitionListener
import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.util.visible
abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListener { abstract class ViewMediaFragment : BaseFragment() {
private var toolbarVisibiltyDisposable: Function0<Boolean>? = null private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
abstract fun setupMediaView(url: String, previewUrl: String?) abstract fun setupMediaView(url: String, previewUrl: String?)
@ -71,6 +70,8 @@ abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListen
} }
} }
abstract fun onTransitionEnd()
protected fun finalizeViewSetup(url: String, previewUrl: String?, description: String?) { protected fun finalizeViewSetup(url: String, previewUrl: String?, description: String?) {
val mediaActivity = activity as ViewMediaActivity val mediaActivity = activity as ViewMediaActivity
setupMediaView(url, previewUrl) setupMediaView(url, previewUrl)

View File

@ -19,7 +19,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -31,6 +30,7 @@ import androidx.annotation.Nullable;
import androidx.arch.core.util.Function; import androidx.arch.core.util.Function;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;

View File

@ -92,9 +92,9 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
publicFiltersPreference = requirePreference("publicFilters") publicFiltersPreference = requirePreference("publicFilters")
threadFiltersPreference = requirePreference("threadFilters") threadFiltersPreference = requirePreference("threadFilters")
notificationPreference.icon = IconicsDrawable(notificationPreference.context, GoogleMaterial.Icon.gmd_notifications).sizePx(iconSize).color(ThemeUtils.getColor(notificationPreference.context, R.attr.toolbar_icon_tint)) notificationPreference.icon = IconicsDrawable(notificationPreference.context, GoogleMaterial.Icon.gmd_notifications).sizePx(iconSize).color(ThemeUtils.getColor(notificationPreference.context, R.attr.preference_icon_tint))
mutedUsersPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp) mutedUsersPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
blockedUsersPreference.icon = IconicsDrawable(blockedUsersPreference.context, GoogleMaterial.Icon.gmd_block).sizePx(iconSize).color(ThemeUtils.getColor(blockedUsersPreference.context, R.attr.toolbar_icon_tint)) blockedUsersPreference.icon = IconicsDrawable(blockedUsersPreference.context, GoogleMaterial.Icon.gmd_block).sizePx(iconSize).color(ThemeUtils.getColor(blockedUsersPreference.context, R.attr.preference_icon_tint))
mutedDomainsPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp) mutedDomainsPreference.icon = getTintedIcon(R.drawable.ic_mute_24dp)
notificationPreference.onPreferenceClickListener = this notificationPreference.onPreferenceClickListener = this
@ -289,7 +289,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
private fun getTintedIcon(iconId: Int): Drawable? { private fun getTintedIcon(iconId: Int): Drawable? {
val drawable = context?.getDrawable(iconId) val drawable = context?.getDrawable(iconId)
ThemeUtils.setDrawableTint(context, drawable, R.attr.toolbar_icon_tint) ThemeUtils.setDrawableTint(context, drawable, R.attr.preference_icon_tint)
return drawable return drawable
} }

View File

@ -39,13 +39,13 @@ class PreferencesFragment : PreferenceFragmentCompat() {
addPreferencesFromResource(R.xml.preferences) addPreferencesFromResource(R.xml.preferences)
val themePreference: Preference = requirePreference("appTheme") val themePreference: Preference = requirePreference("appTheme")
themePreference.icon = IconicsDrawable(themePreference.context, GoogleMaterial.Icon.gmd_palette).sizePx(iconSize).color(ThemeUtils.getColor(themePreference.context, R.attr.toolbar_icon_tint)) themePreference.icon = IconicsDrawable(themePreference.context, GoogleMaterial.Icon.gmd_palette).sizePx(iconSize).color(ThemeUtils.getColor(themePreference.context, R.attr.preference_icon_tint))
val emojiPreference: Preference = requirePreference("emojiCompat") val emojiPreference: Preference = requirePreference("emojiCompat")
emojiPreference.icon = IconicsDrawable(emojiPreference.context, GoogleMaterial.Icon.gmd_sentiment_satisfied).sizePx(iconSize).color(ThemeUtils.getColor(emojiPreference.context, R.attr.toolbar_icon_tint)) emojiPreference.icon = IconicsDrawable(emojiPreference.context, GoogleMaterial.Icon.gmd_sentiment_satisfied).sizePx(iconSize).color(ThemeUtils.getColor(emojiPreference.context, R.attr.preference_icon_tint))
val textSizePreference: Preference = requirePreference("statusTextSize") val textSizePreference: Preference = requirePreference("statusTextSize")
textSizePreference.icon = IconicsDrawable(textSizePreference.context, GoogleMaterial.Icon.gmd_format_size).sizePx(iconSize).color(ThemeUtils.getColor(textSizePreference.context, R.attr.toolbar_icon_tint)) textSizePreference.icon = IconicsDrawable(textSizePreference.context, GoogleMaterial.Icon.gmd_format_size).sizePx(iconSize).color(ThemeUtils.getColor(textSizePreference.context, R.attr.preference_icon_tint))
val timelineFilterPreferences: Preference = requirePreference("timelineFilterPreferences") val timelineFilterPreferences: Preference = requirePreference("timelineFilterPreferences")
timelineFilterPreferences.setOnPreferenceClickListener { timelineFilterPreferences.setOnPreferenceClickListener {
@ -68,11 +68,11 @@ class PreferencesFragment : PreferenceFragmentCompat() {
} }
val languagePreference: Preference = requirePreference("language") val languagePreference: Preference = requirePreference("language")
languagePreference.icon = IconicsDrawable(languagePreference.context, GoogleMaterial.Icon.gmd_translate).sizePx(iconSize).color(ThemeUtils.getColor(languagePreference.context, R.attr.toolbar_icon_tint)) languagePreference.icon = IconicsDrawable(languagePreference.context, GoogleMaterial.Icon.gmd_translate).sizePx(iconSize).color(ThemeUtils.getColor(languagePreference.context, R.attr.preference_icon_tint))
val botIndicatorPreference = requirePreference("showBotOverlay") val botIndicatorPreference = requirePreference("showBotOverlay")
val botDrawable = botIndicatorPreference.context.getDrawable(R.drawable.ic_bot_24dp) val botDrawable = botIndicatorPreference.context.getDrawable(R.drawable.ic_bot_24dp)
ThemeUtils.setDrawableTint(context, botDrawable, R.attr.toolbar_icon_tint) ThemeUtils.setDrawableTint(context, botDrawable, R.attr.preference_icon_tint)
botIndicatorPreference.icon = botDrawable botIndicatorPreference.icon = botDrawable
updateStackTracePreference() updateStackTracePreference()

View File

@ -1,120 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* 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.
*
* Tusky 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 Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.pager;
import android.util.SparseArray;
import android.view.ViewGroup;
import com.keylesspalace.tusky.fragment.AccountMediaFragment;
import com.keylesspalace.tusky.fragment.TimelineFragment;
import com.keylesspalace.tusky.interfaces.RefreshableFragment;
import java.util.HashSet;
import java.util.Set;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
public class AccountPagerAdapter extends FragmentPagerAdapter {
private static final int TAB_COUNT = 4;
private String accountId;
private String[] pageTitles;
private SparseArray<Fragment> fragments = new SparseArray<>(TAB_COUNT);
private final Set<Integer> pagesToRefresh = new HashSet<>();
public AccountPagerAdapter(FragmentManager manager, String accountId) {
super(manager);
this.accountId = accountId;
}
public void setPageTitles(String[] titles) {
pageTitles = titles;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0: {
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId,false);
}
case 1: {
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId,false);
}
case 2: {
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId,false);
}
case 3: {
return AccountMediaFragment.newInstance(accountId,false);
}
default: {
throw new AssertionError("Page " + position + " is out of AccountPagerAdapter bounds");
}
}
}
@Override
public int getCount() {
return TAB_COUNT;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Object fragment = super.instantiateItem(container, position);
if (fragment instanceof Fragment)
fragments.put(position, (Fragment) fragment);
if (pagesToRefresh.contains(position)) {
if (fragment instanceof RefreshableFragment)
((RefreshableFragment) fragment).refreshContent();
pagesToRefresh.remove(position);
}
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.destroyItem(container, position, object);
fragments.remove(position);
}
@Override
public CharSequence getPageTitle(int position) {
return pageTitles[position];
}
@Nullable
public Fragment getFragment(int position) {
return fragments.get(position);
}
public void refreshContent(){
for (int i=0;i<getCount();i++){
Fragment fragment = getFragment(i);
if (fragment instanceof RefreshableFragment){
((RefreshableFragment) fragment).refreshContent();
}
else{
pagesToRefresh.add(i);
}
}
}
}

View File

@ -0,0 +1,65 @@
/* Copyright 2019 Tusky Contributors
*
* This file is a part of Tusky.
*
* 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.
*
* Tusky 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 Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.pager
import androidx.fragment.app.*
import com.keylesspalace.tusky.fragment.AccountMediaFragment
import com.keylesspalace.tusky.fragment.TimelineFragment
import com.keylesspalace.tusky.interfaces.RefreshableFragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import java.lang.ref.WeakReference
class AccountPagerAdapter(
activity: FragmentActivity,
private val accountId: String
) : FragmentStateAdapter(activity) {
private val fragments = MutableList<WeakReference<Fragment>?>(TAB_COUNT) { null }
override fun getItemCount() = TAB_COUNT
override fun createFragment(position: Int): Fragment {
val fragment: Fragment = when (position) {
0 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId, false)
1 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId, false)
2 -> TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId, false)
3 -> AccountMediaFragment.newInstance(accountId, false)
else -> throw AssertionError("Page $position is out of AccountPagerAdapter bounds")
}
fragments[position] = WeakReference(fragment)
return fragment
}
fun getFragment(position: Int): Fragment? {
return fragments[position]?.get()
}
fun refreshContent() {
for (i in 0 until TAB_COUNT) {
val fragment = getFragment(i)
if (fragment != null && fragment is RefreshableFragment) {
(fragment as RefreshableFragment).refreshContent()
}
}
}
companion object {
private const val TAB_COUNT = 4
}
}

View File

@ -1,13 +1,16 @@
package com.keylesspalace.tusky.pager package com.keylesspalace.tusky.pager
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentPagerAdapter import com.keylesspalace.tusky.ViewMediaAdapter
import com.keylesspalace.tusky.SharedElementTransitionListener
import com.keylesspalace.tusky.fragment.ViewMediaFragment import com.keylesspalace.tusky.fragment.ViewMediaFragment
class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avatarUrl: String) : FragmentPagerAdapter(fragmentManager), SharedElementTransitionListener { class AvatarImagePagerAdapter(
override fun getItem(position: Int): Fragment { activity: FragmentActivity,
private val avatarUrl: String
) : ViewMediaAdapter(activity) {
override fun createFragment(position: Int): Fragment {
return if (position == 0) { return if (position == 0) {
ViewMediaFragment.newAvatarInstance(avatarUrl) ViewMediaFragment.newAvatarInstance(avatarUrl)
} else { } else {
@ -15,8 +18,8 @@ class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avat
} }
} }
override fun getCount() = 1 override fun getItemCount() = 1
override fun onTransitionEnd() { override fun onTransitionEnd(position: Int) {
} }
} }

View File

@ -1,53 +1,42 @@
package com.keylesspalace.tusky.pager package com.keylesspalace.tusky.pager
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentStatePagerAdapter import com.keylesspalace.tusky.ViewMediaAdapter
import com.keylesspalace.tusky.SharedElementTransitionListener
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.fragment.ViewMediaFragment import com.keylesspalace.tusky.fragment.ViewMediaFragment
import java.util.* import java.lang.ref.WeakReference
class ImagePagerAdapter( class ImagePagerAdapter(
fragmentManager: FragmentManager, activity: FragmentActivity,
private val attachments: List<Attachment>, private val attachments: List<Attachment>,
private val initialPosition: Int private val initialPosition: Int
) : FragmentStatePagerAdapter(fragmentManager), SharedElementTransitionListener { ) : ViewMediaAdapter(activity) {
private var primaryItem: ViewMediaFragment? = null
private var didTransition = false private var didTransition = false
private val fragments = MutableList<WeakReference<ViewMediaFragment>?>(attachments.size) { null }
override fun setPrimaryItem(container: ViewGroup, position: Int, item: Any) { override fun getItemCount() = attachments.size
super.setPrimaryItem(container, position, item)
this.primaryItem = item as ViewMediaFragment
}
override fun getItem(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return if (position >= 0 && position < attachments.size) { if (position >= 0 && position < attachments.size) {
// Fragment should not wait for or start transition if it already happened but we // Fragment should not wait for or start transition if it already happened but we
// instantiate the same fragment again, e.g. open the first photo, scroll to the // instantiate the same fragment again, e.g. open the first photo, scroll to the
// forth photo and then back to the first. The first fragment will trz to start the // forth photo and then back to the first. The first fragment will try to start the
// transition and wait until it's over and it will never take place. // transition and wait until it's over and it will never take place.
ViewMediaFragment.newInstance( val fragment = ViewMediaFragment.newInstance(
attachment = attachments[position], attachment = attachments[position],
shouldStartPostponedTransition = !didTransition && position == initialPosition shouldStartPostponedTransition = !didTransition && position == initialPosition
) )
fragments[position] = WeakReference(fragment)
return fragment
} else { } else {
throw IllegalStateException() throw IllegalStateException()
} }
} }
override fun getCount(): Int { override fun onTransitionEnd(position: Int) {
return attachments.size
}
override fun getPageTitle(position: Int): CharSequence {
return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size)
}
override fun onTransitionEnd() {
this.didTransition = true this.didTransition = true
primaryItem?.onTransitionEnd() fragments[position]?.get()?.onTransitionEnd()
} }
} }

View File

@ -15,49 +15,23 @@
package com.keylesspalace.tusky.pager package com.keylesspalace.tusky.pager
import android.util.SparseArray
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentPagerAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager.widget.PagerAdapter
import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.TabData
import java.lang.ref.WeakReference
class MainPagerAdapter(val tabs: List<TabData>, manager: FragmentManager) : FragmentPagerAdapter(manager) { class MainPagerAdapter(val tabs: List<TabData>, activity: FragmentActivity) : FragmentStateAdapter(activity) {
private val fragments = SparseArray<Fragment>(tabs.size) private val fragments = MutableList<WeakReference<Fragment>?>(tabs.size) { null }
override fun getItem(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
val tab = tabs[position] val tab = tabs[position]
return tab.fragment(tab.arguments) val fragment = tab.fragment(tab.arguments)
} fragments[position] = WeakReference(fragment)
override fun getCount(): Int {
return tabs.size
}
override fun getPageTitle(position: Int): CharSequence? {
return null
}
override fun getItemId(position: Int): Long {
return tabs[position].hashCode() + position.toLong()
}
override fun getItemPosition(item: Any): Int {
return PagerAdapter.POSITION_NONE
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position)
if (fragment is Fragment)
fragments.put(position, fragment)
return fragment return fragment
} }
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { override fun getItemCount() = tabs.size
super.destroyItem(container, position, `object`)
fragments.remove(position)
}
fun getFragment(position: Int): Fragment? = fragments[position] fun getFragment(position: Int): Fragment? = fragments[position]?.get()
} }

View File

@ -1,65 +0,0 @@
/* Copyright 2019 Levi Bard
*
* This file is a part of Tusky.
*
* 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.
*
* Tusky 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 Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.service
import android.annotation.TargetApi
import android.content.ComponentName
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.os.Bundle
import android.service.chooser.ChooserTarget
import android.service.chooser.ChooserTargetService
import android.text.TextUtils
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.TuskyApplication
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.NotificationHelper
@TargetApi(23)
class AccountChooserService : ChooserTargetService(), Injectable {
// cannot inject here, it crashes on APIs < 23
lateinit var accountManager: AccountManager
override fun onCreate() {
super.onCreate()
accountManager = (application as TuskyApplication).serviceLocator.get(AccountManager::class.java)
}
override fun onGetChooserTargets(targetActivityName: ComponentName?, intentFilter: IntentFilter?): MutableList<ChooserTarget> {
val targets = mutableListOf<ChooserTarget>()
for (account in accountManager.getAllAccountsOrderedByActive()) {
val icon: Icon = if (TextUtils.isEmpty(account.profilePictureUrl)) {
Icon.createWithResource(applicationContext, R.drawable.avatar_default)
} else {
val bmp = Glide.with(this)
.asBitmap()
.load(account.profilePictureUrl)
.error(R.drawable.avatar_default)
.placeholder(R.drawable.avatar_default)
.submit()
Icon.createWithBitmap(bmp.get())
}
val bundle = Bundle()
bundle.putLong(NotificationHelper.ACCOUNT_ID, account.id)
targets.add(ChooserTarget(account.displayName, icon, 1.0f, targetActivityName, bundle))
}
return targets
}
}

View File

@ -1,4 +1,4 @@
/* Copyright 2017 Andrew Dawson /* Copyright 2019 Tusky Contributors
* *
* This file is a part of Tusky. * This file is a part of Tusky.
* *
@ -13,29 +13,28 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.service; package com.keylesspalace.tusky.service
import android.annotation.TargetApi; import android.annotation.TargetApi
import android.content.Intent; import android.content.Intent
import android.service.quicksettings.TileService; import android.service.quicksettings.TileService
import com.keylesspalace.tusky.ComposeActivity; import com.keylesspalace.tusky.MainActivity
/** /**
* Small Addition that adds in a QuickSettings tile that opens the Compose activity when clicked * Small Addition that adds in a QuickSettings tile
* Created by ztepps on 4/3/17. * opens the Compose activity or shows an account selector when multiple accounts are present
*/ */
@TargetApi(24) @TargetApi(24)
public class TuskyTileService extends TileService { class TuskyTileService : TileService() {
public TuskyTileService() {
super();
}
@Override override fun onClick() {
public void onClick() { val intent = Intent(this, MainActivity::class.java).apply {
Intent intent = new Intent(this, ComposeActivity.class); addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); action = Intent.ACTION_SEND
startActivityAndCollapse(intent); type = "text/plain"
}
startActivityAndCollapse(intent)
} }
} }

View File

@ -5,12 +5,12 @@ package com.keylesspalace.tusky.util
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.Px import androidx.annotation.Px
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
private val fitCenterTransformation = FitCenter() private val centerCropTransformation = CenterCrop()
fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boolean) { fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boolean) {
@ -23,7 +23,7 @@ fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boo
Glide.with(imageView) Glide.with(imageView)
.load(url) .load(url)
.transform( .transform(
fitCenterTransformation, centerCropTransformation,
RoundedCorners(radius) RoundedCorners(radius)
) )
.placeholder(R.drawable.avatar_default) .placeholder(R.drawable.avatar_default)
@ -34,7 +34,7 @@ fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boo
.asBitmap() .asBitmap()
.load(url) .load(url)
.transform( .transform(
fitCenterTransformation, centerCropTransformation,
RoundedCorners(radius) RoundedCorners(radius)
) )
.placeholder(R.drawable.avatar_default) .placeholder(R.drawable.avatar_default)

View File

@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
@ -32,6 +31,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.preference.PreferenceManager;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;

View File

@ -18,9 +18,8 @@ package com.keylesspalace.tusky.util
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.*
import java.util.Locale
class LocaleManager(context: Context) { class LocaleManager(context: Context) {

View File

@ -69,6 +69,8 @@ import java.util.concurrent.ExecutionException;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
public class NotificationHelper { public class NotificationHelper {
private static int notificationId = 0; private static int notificationId = 0;
@ -293,6 +295,7 @@ public class NotificationHelper {
.setColor(BuildConfig.DEBUG ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue)) .setColor(BuildConfig.DEBUG ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue))
.setGroup(account.getAccountId()) .setGroup(account.getAccountId())
.setAutoCancel(true) .setAutoCancel(true)
.setShortcutId(Long.toString(account.getId()))
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback .setDefaults(0); // So it doesn't ring twice, notify only in Target callback
setupPreferences(account, builder); setupPreferences(account, builder);
@ -627,9 +630,9 @@ public class NotificationHelper {
builder.append('\n'); builder.append('\n');
Poll poll = notification.getStatus().getPoll(); Poll poll = notification.getStatus().getPoll();
for(PollOption option: poll.getOptions()) { for(PollOption option: poll.getOptions()) {
int percent = PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount()); builder.append(buildDescription(option.getTitle(),
CharSequence optionText = HtmlUtils.fromHtml(context.getString(R.string.poll_option_format, percent, option.getTitle())); PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount()),
builder.append(optionText); context));
builder.append('\n'); builder.append('\n');
} }
return builder.toString(); return builder.toString();

View File

@ -18,7 +18,9 @@ package com.keylesspalace.tusky.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.BuildConfig;
@ -26,7 +28,6 @@ import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;

View File

@ -0,0 +1,102 @@
/* Copyright 2019 Tusky Contributors
*
* This file is a part of Tusky.
*
* 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.
*
* Tusky 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 Tusky; if not,
* see <http://www.gnu.org/licenses>. */
@file:JvmName("ShareShortcutHelper")
package com.keylesspalace.tusky.util
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.text.TextUtils
import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.bumptech.glide.Glide
import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.db.AccountEntity
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
fun updateShortcut(context: Context, account: AccountEntity) {
Single.fromCallable {
val innerSize = context.resources.getDimensionPixelSize(R.dimen.adaptive_bitmap_inner_size)
val outerSize = context.resources.getDimensionPixelSize(R.dimen.adaptive_bitmap_outer_size)
val bmp = if (TextUtils.isEmpty(account.profilePictureUrl)) {
Glide.with(context)
.asBitmap()
.load(R.drawable.avatar_default)
.submit(innerSize, innerSize)
.get()
} else {
Glide.with(context)
.asBitmap()
.load(account.profilePictureUrl)
.error(R.drawable.avatar_default)
.submit(innerSize, innerSize)
.get()
}
// inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon
val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888)
val canvas = Canvas(outBmp)
canvas.drawBitmap(bmp, (outerSize - innerSize).toFloat() / 2f, (outerSize - innerSize).toFloat() / 2f, null)
val icon = IconCompat.createWithAdaptiveBitmap(outBmp)
val person = Person.Builder()
.setIcon(icon)
.setName(account.displayName)
.setKey(account.identifier)
.build()
// This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different
val intent = Intent(context, MainActivity::class.java).apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(NotificationHelper.ACCOUNT_ID, account.id)
}
val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString())
.setIntent(intent)
.setCategories(setOf("com.keylesspalace.tusky.Share"))
.setShortLabel(account.displayName)
.setPerson(person)
.setLongLived(true)
.setIcon(icon)
.build()
ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo))
}
.subscribeOn(Schedulers.io())
.onErrorReturnItem(false)
.subscribe()
}
fun removeShortcut(context: Context, account: AccountEntity) {
ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(account.id.toString()))
}

View File

@ -30,6 +30,7 @@ import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.view.MediaPreviewImageView import com.keylesspalace.tusky.view.MediaPreviewImageView
import com.keylesspalace.tusky.viewdata.PollViewData import com.keylesspalace.tusky.viewdata.PollViewData
import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent import com.keylesspalace.tusky.viewdata.calculatePercent
import java.text.NumberFormat import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -283,8 +284,8 @@ class StatusViewHelper(private val itemView: View) {
if (i < options.size) { if (i < options.size) {
val percent = calculatePercent(options[i].votesCount, poll.votesCount) val percent = calculatePercent(options[i].votesCount, poll.votesCount)
val pollOptionText = pollResults[i].context.getString(R.string.poll_option_format, percent, options[i].title) val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context)
pollResults[i].text = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, pollResults[i]) pollResults[i].text = CustomEmojiHelper.emojifyText(pollOptionText, emojis, pollResults[i])
pollResults[i].visibility = View.VISIBLE pollResults[i].visibility = View.VISIBLE
val level = percent * 100 val level = percent * 100

View File

@ -29,27 +29,21 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import android.util.TypedValue;
import javax.inject.Inject;
import javax.inject.Singleton;
/** /**
* Provides runtime compatibility to obtain theme information and re-theme views, especially where * Provides runtime compatibility to obtain theme information and re-theme views, especially where
* the ability to do so is not supported in resource files. * the ability to do so is not supported in resource files.
*/ */
@Singleton
public class ThemeUtils { public class ThemeUtils {
@Inject
public ThemeUtils(){}
public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT; public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT;
private static final String THEME_NIGHT = "night"; private static final String THEME_NIGHT = "night";
public static final String THEME_DAY = "day"; public static final String THEME_DAY = "day";
private static final String THEME_BLACK = "black"; private static final String THEME_BLACK = "black";
private static final String THEME_AUTO = "auto"; private static final String THEME_AUTO = "auto";
public static final String THEME_SYSTEM = "auto_system"; private static final String THEME_SYSTEM = "auto_system";
public static Drawable getDrawable(@NonNull Context context, @AttrRes int attribute, public static Drawable getDrawable(@NonNull Context context, @AttrRes int attribute,
@DrawableRes int fallbackDrawable) { @DrawableRes int fallbackDrawable) {
@ -63,8 +57,9 @@ public class ThemeUtils {
return context.getDrawable(resourceId); return context.getDrawable(resourceId);
} }
public static @DrawableRes int getDrawableId(@NonNull Context context, @AttrRes int attribute, @DrawableRes
@DrawableRes int fallbackDrawableId) { public static int getDrawableId(@NonNull Context context, @AttrRes int attribute,
@DrawableRes int fallbackDrawableId) {
TypedValue value = new TypedValue(); TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) { if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.resourceId; return value.resourceId;
@ -73,7 +68,8 @@ public class ThemeUtils {
} }
} }
public static @ColorInt int getColor(@NonNull Context context, @AttrRes int attribute) { @ColorInt
public static int getColor(@NonNull Context context, @AttrRes int attribute) {
TypedValue value = new TypedValue(); TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) { if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.data; return value.data;
@ -82,14 +78,16 @@ public class ThemeUtils {
} }
} }
public static @ColorRes int getColorId(@NonNull Context context, @AttrRes int attribute) { @ColorRes
public static int getColorId(@NonNull Context context, @AttrRes int attribute) {
TypedValue value = new TypedValue(); TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(attribute, value, true); context.getTheme().resolveAttribute(attribute, value, true);
return value.resourceId; return value.resourceId;
} }
/** this can be replaced with drawableTint in xml once minSdkVersion >= 23 */ /** this can be replaced with drawableTint in xml once minSdkVersion >= 23 */
public static @Nullable Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableId, @AttrRes int colorAttr) { @Nullable
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableId, @AttrRes int colorAttr) {
Drawable drawable = context.getDrawable(drawableId); Drawable drawable = context.getDrawable(drawableId);
if(drawable == null) { if(drawable == null) {
return null; return null;
@ -102,30 +100,21 @@ public class ThemeUtils {
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN); drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
} }
public void setAppNightMode(String flavor, Context context) { public static void setAppNightMode(String flavor) {
switch (flavor) { switch (flavor) {
default: default:
case THEME_NIGHT: case THEME_NIGHT:
case THEME_BLACK:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
break; break;
case THEME_DAY: case THEME_DAY:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
break; break;
case THEME_BLACK:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
break;
case THEME_AUTO: case THEME_AUTO:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_TIME);
break; break;
case THEME_SYSTEM: case THEME_SYSTEM:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
//stupid workaround to make MODE_NIGHT_FOLLOW_SYSTEM work :(
if((Settings.System.getInt(context.getContentResolver(), "display_night_theme", 0) == 1)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else if ((Settings.System.getInt(context.getContentResolver(), "display_night_theme", 0) == 0)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
break; break;
} }
} }

View File

@ -120,7 +120,8 @@ public class ComposeScheduleView extends ConstraintLayout {
private void openPickDateDialog() { private void openPickDateDialog() {
long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000; long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000;
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder() CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
.setValidator(new DateValidatorPointForward(yesterday)) .setValidator(
DateValidatorPointForward.from(yesterday))
.build(); .build();
if (scheduleDateTime == null) { if (scheduleDateTime == null) {
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());

View File

@ -1,45 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* 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.
*
* Tusky 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 Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view;
import android.content.Context;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* This class is entirely to address a known issue with com.github.chrisbanes.photoview.PhotoView.
* ViewPager will throw exceptions when a PhotoView is placed within it, so this subclass eats those
* exceptions.
*/
public class ImageViewPager extends ViewPager {
public ImageViewPager(Context context) {
super(context);
}
public ImageViewPager(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
return false;
}
}
}

View File

@ -15,8 +15,13 @@
package com.keylesspalace.tusky.viewdata package com.keylesspalace.tusky.viewdata
import android.content.Context
import android.text.SpannableStringBuilder
import android.text.Spanned
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.PollOption import com.keylesspalace.tusky.entity.PollOption
import com.keylesspalace.tusky.util.HtmlUtils
import java.util.* import java.util.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -44,6 +49,12 @@ fun calculatePercent(fraction: Int, total: Int): Int {
} }
} }
fun buildDescription(title: String, percent: Int, context: Context): Spanned {
return SpannableStringBuilder(HtmlUtils.fromHtml(context.getString(R.string.poll_percent_format, percent)))
.append(" ")
.append(title)
}
fun Poll?.toViewData(): PollViewData? { fun Poll?.toViewData(): PollViewData? {
if (this == null) return null if (this == null) return null
return PollViewData( return PollViewData(

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="false" android:color="?attr/toolbar_icon_tint"/>
<item android:state_selected="true" android:color="?attr/tab_icon_selected_tint"/>
</selector>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_primary_dark_dark" />
<corners android:radius="14dp"/>
<size android:height="100dp" android:width="100dp"/>
</shape>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_background_light" />
<corners android:radius="14dp"/>
<size android:height="100dp" android:width="100dp"/>
</shape>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/tusky_blue"
android:pathData="M17,2L17,5L5,5L5,11L7,11L7,7L17,7L17,10L21,6L17,2zM9.75,9.75L9.75,14.25L14.25,14.25L14.25,9.75L9.75,9.75zM17,13L17,17L7,17L7,14L3,18L7,22L7,19L19,19L19,13L17,13z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/tusky_blue"
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
</vector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/tab_page_margin_black" />
</shape>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/tab_page_margin_dark" />
</shape>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/tab_page_margin_light" />
</shape>

View File

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="?attr/tab_page_margin_drawable">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="640dp" android:layout_width="640dp"
@ -45,16 +44,17 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/elephant_error" tools:src="@drawable/elephant_error"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/topProgressBar" android:id="@+id/topProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
app:layout_constraintTop_toTopOf="parent"
android:indeterminate="true" android:indeterminate="true"
app:layout_constraintStart_toStartOf="parent" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="parent" app:layout_constraintBottom_toTopOf="parent"
android:visibility="gone"/> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="?attr/tab_page_margin_drawable">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="640dp" android:layout_width="640dp"
@ -80,9 +79,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@android:color/transparent"
android:visibility="gone" android:visibility="gone"
tools:src="@drawable/elephant_error"
tools:visibility="visible" /> tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout> </FrameLayout>

View File

@ -1,20 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="?attr/tab_page_margin_drawable">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="640dp" android:layout_width="640dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal">
android:background="?attr/window_background">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/window_background"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -16,8 +16,8 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textDirection="anyRtl" android:layout_gravity="center"
android:layout_gravity="center"> android:textDirection="anyRtl">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -46,11 +46,24 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="60dp" /> android:layout_height="60dp" />
<TextView
android:id="@+id/aboutPoweredByTusky"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:textSize="18sp"
android:text="@string/about_powered_by_tusky"
android:textStyle="bold" />
<TextView <TextView
android:id="@+id/aboutLicenseInfoTextView" android:id="@+id/aboutLicenseInfoTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:paddingStart="@dimen/text_content_margin" android:paddingStart="@dimen/text_content_margin"
android:paddingEnd="@dimen/text_content_margin" android:paddingEnd="@dimen/text_content_margin"

View File

@ -17,16 +17,15 @@
android:id="@+id/accountAppBarLayout" android:id="@+id/accountAppBarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:colorBackground"
android:elevation="@dimen/actionbar_elevation"> android:elevation="@dimen/actionbar_elevation">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar" android:id="@+id/collapsingToolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:contentScrim="?attr/toolbar_background_color" app:contentScrim="?attr/colorSurface"
app:layout_scrollFlags="scroll|exitUntilCollapsed" app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:statusBarScrim="?android:attr/colorBackground" app:statusBarScrim="?attr/colorSurface"
app:titleEnabled="false"> app:titleEnabled="false">
<ImageView <ImageView
@ -64,7 +63,6 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="@android:color/white"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -174,7 +172,7 @@
android:id="@+id/accountNoteTextView" android:id="@+id/accountNoteTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:paddingTop="10dp" android:paddingTop="10dp"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
@ -196,6 +194,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:text="@string/label_remote_account" android:text="@string/label_remote_account"
android:visibility="gone" android:visibility="gone"
@ -307,7 +306,6 @@
android:textSize="?attr/status_text_medium" /> android:textSize="?attr/status_text_medium" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<!-- top margin equal to statusbar size will be set programmatically --> <!-- top margin equal to statusbar size will be set programmatically -->
@ -317,8 +315,10 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:layout_gravity="top" android:layout_gravity="top"
android:background="@android:color/transparent" android:background="@android:color/transparent"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin" app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways" /> app:layout_scrollFlags="scroll|enterAlways" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
@ -333,10 +333,11 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/accountFragmentViewPager" android:id="@+id/accountFragmentViewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/tab_page_margin_color"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
@ -355,11 +356,12 @@
android:layout_width="@dimen/account_activity_avatar_size" android:layout_width="@dimen/account_activity_avatar_size"
android:layout_height="@dimen/account_activity_avatar_size" android:layout_height="@dimen/account_activity_avatar_size"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:background="@drawable/avatar_background"
android:padding="3dp" android:padding="3dp"
app:layout_anchor="@+id/accountHeaderInfoContainer" app:layout_anchor="@+id/accountHeaderInfoContainer"
app:layout_anchorGravity="top" app:layout_anchorGravity="top"
app:layout_scrollFlags="scroll" app:layout_scrollFlags="scroll"
app:srcCompat="@drawable/avatar_default" /> app:srcCompat="@drawable/avatar_default" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -27,6 +27,7 @@
android:layout_height="178dp" android:layout_height="178dp"
android:layout_marginBottom="50dp" android:layout_marginBottom="50dp"
android:contentDescription="@null" android:contentDescription="@null"
android:id="@+id/loginLogo"
app:srcCompat="@drawable/elephant_friend" /> app:srcCompat="@drawable/elephant_friend" />
<LinearLayout <LinearLayout

View File

@ -11,53 +11,43 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" > android:layout_weight="1">
<RelativeLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:clipChildren="false"> android:elevation="@dimen/actionbar_elevation">
<ImageButton <androidx.appcompat.widget.Toolbar
android:id="@+id/drawer_toggle" android:id="@+id/main_toolbar"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="?android:colorBackground"
android:contentDescription="@string/action_open_drawer"
android:elevation="@dimen/actionbar_elevation"
app:srcCompat="@drawable/ic_menu_24dp" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
style="@style/TuskyTabAppearance"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_toEndOf="@id/drawer_toggle"
android:background="?android:colorBackground"
android:focusable="true"
android:focusableInTouchMode="true"
android:elevation="@dimen/actionbar_elevation"
app:tabGravity="fill"
app:tabIconTint="@color/tab_icon_color"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabPaddingEnd="1dp"
app:tabPaddingStart="1dp"
app:tabPaddingTop="4dp"
app:tabUnboundedRipple="false" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/tab_layout" app:contentInsetStartWithNavigation="0dp">
android:layout_alignParentBottom="true" />
</RelativeLayout> <com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:focusable="true"
android:focusableInTouchMode="true"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tab_layout"
android:background="?attr/tab_page_margin_color"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_btn" android:id="@+id/floating_btn"
@ -73,8 +63,9 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<include layout="@layout/view_quick_toot" <include
android:id="@+id/quick_toot_container" android:id="@+id/quick_toot_container"
layout="@layout/view_quick_toot"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" /> android:layout_weight="0" />

View File

@ -10,29 +10,29 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="@dimen/actionbar_elevation" android:elevation="@dimen/actionbar_elevation"
android:stateListAnimator="@null"
app:layout_collapseMode="pin"> app:layout_collapseMode="pin">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/toolbar_background_color"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
android:elevation="@dimen/actionbar_elevation"
app:layout_scrollFlags="scroll|snap|enterAlways" app:layout_scrollFlags="scroll|snap|enterAlways"
app:navigationIcon="?attr/homeAsUpIndicator" /> app:navigationIcon="?attr/homeAsUpIndicator" />
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tabs" android:id="@+id/tabs"
style="@style/TuskyTabAppearance" style="@style/TuskyTabAppearance"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/toolbar_background_color"
app:tabGravity="fill" app:tabGravity="fill"
app:tabMode="fixed" app:tabMode="fixed"
app:tabTextAppearance="@style/TuskyTabAppearance"/> app:tabTextAppearance="@style/TuskyTabAppearance"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/pages" android:id="@+id/pages"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -6,7 +6,7 @@
android:background="@android:color/black" android:background="@android:color/black"
android:orientation="vertical"> android:orientation="vertical">
<com.keylesspalace.tusky.view.ImageViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager" android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View File

@ -4,20 +4,20 @@
android:id="@+id/layoutRoot" android:id="@+id/layoutRoot"
android:layout_width="@dimen/timeline_width" android:layout_width="@dimen/timeline_width"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/tab_page_margin_drawable"> android:background="?attr/tab_page_margin_color">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchRecyclerView" android:id="@+id/searchRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/window_background" android:background="?attr/window_background"
tools:listitem="@layout/item_account"/> tools:listitem="@layout/item_account" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -3,7 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?attr/window_background">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -29,23 +31,22 @@
android:id="@+id/statusView" android:id="@+id/statusView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@android:color/transparent"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/elephant_error" tools:visibility="visible" />
tools:visibility="visible"/>
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/topProgressBar" android:id="@+id/topProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
app:layout_constraintTop_toTopOf="parent"
android:indeterminate="true" android:indeterminate="true"
app:layout_constraintStart_toStartOf="parent" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="parent" app:layout_constraintBottom_toTopOf="parent"
android:visibility="gone"/> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -58,7 +58,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:background="?attr/window_background" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
@ -17,25 +17,26 @@
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_gravity="center" /> android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/mediaDescription" android:id="@+id/mediaDescription"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
android:background="#60000000" android:background="#60000000"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:padding="8dp" android:padding="8dp"
android:textAlignment="center" android:textAlignment="center"
android:textColor="#eee" android:textColor="#eee"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Some media description" /> tools:text="Some media description" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,5 +9,7 @@
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/window_background"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -2,9 +2,9 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/videoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/videoContainer"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@ -14,6 +14,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize" android:layout_marginTop="?attr/actionBarSize"
android:background="#60000000" android:background="#60000000"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:padding="8dp" android:padding="8dp"
android:textAlignment="center" android:textAlignment="center"

View File

@ -36,8 +36,9 @@
android:contentDescription="@string/action_view_profile" android:contentDescription="@string/action_view_profile"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/notification_username"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_text" app:layout_constraintTop_toTopOf="@id/notification_display_name"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
@ -52,7 +53,8 @@
android:textStyle="normal|bold" android:textStyle="normal|bold"
app:layout_constraintBottom_toTopOf="@id/notification_username" app:layout_constraintBottom_toTopOf="@id/notification_username"
app:layout_constraintStart_toEndOf="@id/notification_avatar" app:layout_constraintStart_toEndOf="@id/notification_avatar"
app:layout_constraintTop_toTopOf="@id/notification_avatar" app:layout_constraintTop_toBottomOf="@id/notification_text"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Test User" /> tools:text="Test User" />
<TextView <TextView
@ -64,7 +66,7 @@
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toBottomOf="@id/notification_avatar" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/notification_avatar" app:layout_constraintStart_toEndOf="@id/notification_avatar"
app:layout_constraintTop_toBottomOf="@id/notification_display_name" app:layout_constraintTop_toBottomOf="@id/notification_display_name"
tools:text="\@testuser" /> tools:text="\@testuser" />

View File

@ -106,6 +106,7 @@
android:id="@+id/status_content_warning_description" android:id="@+id/status_content_warning_description"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hyphenationFrequency="full"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -146,6 +147,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:focusable="true" android:focusable="true"
android:hyphenationFrequency="full"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -448,7 +450,7 @@
app:layout_constraintEnd_toStartOf="@id/status_favourite" app:layout_constraintEnd_toStartOf="@id/status_favourite"
app:layout_constraintStart_toEndOf="@id/status_reply" app:layout_constraintStart_toEndOf="@id/status_reply"
app:layout_constraintTop_toTopOf="@id/status_reply" app:layout_constraintTop_toTopOf="@id/status_reply"
sparkbutton:activeImage="@drawable/reblog_active" sparkbutton:activeImage="@drawable/ic_reblog_active_24dp"
sparkbutton:iconSize="28dp" sparkbutton:iconSize="28dp"
sparkbutton:inactiveImage="?attr/status_reblog_inactive_drawable" sparkbutton:inactiveImage="?attr/status_reblog_inactive_drawable"
sparkbutton:primaryColor="@color/tusky_blue" sparkbutton:primaryColor="@color/tusky_blue"

View File

@ -84,6 +84,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hyphenationFrequency="full"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -120,6 +121,7 @@
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:focusable="true" android:focusable="true"
android:hyphenationFrequency="full"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
@ -536,7 +538,7 @@
app:layout_constraintEnd_toStartOf="@id/status_favourite" app:layout_constraintEnd_toStartOf="@id/status_favourite"
app:layout_constraintStart_toEndOf="@id/status_reply" app:layout_constraintStart_toEndOf="@id/status_reply"
app:layout_constraintTop_toTopOf="@id/status_reply" app:layout_constraintTop_toTopOf="@id/status_reply"
sparkbutton:activeImage="@drawable/reblog_active" sparkbutton:activeImage="@drawable/ic_reblog_active_24dp"
sparkbutton:iconSize="28dp" sparkbutton:iconSize="28dp"
sparkbutton:inactiveImage="?attr/status_reblog_inactive_drawable" sparkbutton:inactiveImage="?attr/status_reblog_inactive_drawable"
sparkbutton:primaryColor="@color/tusky_blue" sparkbutton:primaryColor="@color/tusky_blue"

View File

@ -13,8 +13,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginBottom="6dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="6dp"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -39,8 +39,8 @@
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:paddingEnd="@dimen/status_display_name_padding_end"
android:paddingStart="0dp" android:paddingStart="0dp"
android:paddingEnd="@dimen/status_display_name_padding_end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
@ -51,8 +51,8 @@
android:id="@+id/status_username" android:id="@+id/status_username"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@id/status_display_name"
android:layout_toStartOf="@+id/status_timestamp_info" android:layout_toStartOf="@+id/status_timestamp_info"
android:layout_toEndOf="@id/status_display_name"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
@ -77,27 +77,27 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/status_name_bar" android:layout_below="@id/status_name_bar"
android:layout_toEndOf="@id/notification_status_avatar" android:layout_toEndOf="@id/notification_status_avatar"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
tools:text="Example CW text" /> tools:text="Example CW text" />
<ToggleButton <ToggleButton
android:id="@+id/notification_content_warning_button" android:id="@+id/notification_content_warning_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/notification_content_warning_description" android:layout_below="@+id/notification_content_warning_description"
android:layout_toEndOf="@id/notification_status_avatar"
android:background="?attr/content_warning_button"
android:minHeight="0dp"
android:minWidth="150dp"
android:paddingBottom="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="4dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:layout_toEndOf="@id/notification_status_avatar"
android:background="?attr/content_warning_button"
android:minWidth="150dp"
android:minHeight="0dp"
android:paddingLeft="16dp"
android:paddingTop="4dp"
android:paddingRight="16dp"
android:paddingBottom="4dp"
android:textAllCaps="true" android:textAllCaps="true"
android:textOff="@string/status_content_warning_show_more" android:textOff="@string/status_content_warning_show_more"
android:textOn="@string/status_content_warning_show_less" android:textOn="@string/status_content_warning_show_less"
@ -109,6 +109,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/notification_content_warning_button" android:layout_below="@id/notification_content_warning_button"
android:layout_toEndOf="@+id/notification_status_avatar" android:layout_toEndOf="@+id/notification_status_avatar"
android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:paddingBottom="10dp" android:paddingBottom="10dp"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
@ -117,25 +118,25 @@
<ToggleButton <ToggleButton
android:id="@+id/button_toggle_notification_content" android:id="@+id/button_toggle_notification_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toEndOf="@id/notification_status_avatar" android:layout_below="@id/notification_content"
android:layout_below="@id/notification_content" android:layout_marginTop="4dp"
android:textOff="@string/status_content_show_less" android:layout_marginBottom="4dp"
android:textOn="@string/status_content_show_more" android:layout_toEndOf="@id/notification_status_avatar"
android:background="?attr/content_warning_button" android:background="?attr/content_warning_button"
android:minHeight="0dp" android:minWidth="150dp"
android:minWidth="150dp" android:minHeight="0dp"
android:paddingBottom="4dp" android:paddingLeft="16dp"
android:paddingLeft="16dp" android:paddingTop="4dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingTop="4dp" android:paddingBottom="4dp"
android:layout_marginTop="4dp" android:textAllCaps="true"
android:layout_marginBottom="4dp" android:textOff="@string/status_content_show_less"
android:textAllCaps="true" android:textOn="@string/status_content_show_more"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
android:visibility="gone" /> android:visibility="gone" />
<include <include
android:id="@+id/status_quote_inline_container" android:id="@+id/status_quote_inline_container"
@ -151,13 +152,13 @@
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_below="@id/notification_top_text" android:layout_below="@id/notification_top_text"
android:layout_marginBottom="14dp" android:layout_marginTop="10dp"
android:layout_marginEnd="14dp" android:layout_marginEnd="14dp"
android:layout_marginRight="14dp" android:layout_marginRight="14dp"
android:layout_marginTop="10dp" android:layout_marginBottom="14dp"
android:contentDescription="@string/action_view_profile" android:contentDescription="@string/action_view_profile"
android:paddingBottom="12dp"
android:paddingRight="12dp" android:paddingRight="12dp"
android:paddingBottom="12dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:ignore="RtlHardcoded,RtlSymmetry" tools:ignore="RtlHardcoded,RtlSymmetry"
tools:src="@drawable/avatar_default" /> tools:src="@drawable/avatar_default" />
@ -166,7 +167,7 @@
android:id="@+id/notification_notification_avatar" android:id="@+id/notification_notification_avatar"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_alignBottom="@+id/notification_status_avatar" android:layout_alignEnd="@id/notification_status_avatar"
android:layout_alignEnd="@id/notification_status_avatar" /> android:layout_alignBottom="@+id/notification_status_avatar" />
</RelativeLayout> </RelativeLayout>

View File

@ -12,8 +12,7 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize" />
android:background="?attr/toolbar_background_color" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -26,7 +26,6 @@
<string name="title_direct_messages">الرسائل المباشرة</string> <string name="title_direct_messages">الرسائل المباشرة</string>
<string name="title_tab_preferences">الألسنة</string> <string name="title_tab_preferences">الألسنة</string>
<string name="title_view_thread">تبويق</string> <string name="title_view_thread">تبويق</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">المشاركات</string> <string name="title_statuses">المشاركات</string>
<string name="title_statuses_with_replies">يحتوي على ردود</string> <string name="title_statuses_with_replies">يحتوي على ردود</string>
<string name="title_statuses_pinned">مدبّس</string> <string name="title_statuses_pinned">مدبّس</string>
@ -483,4 +482,12 @@
<string name="filter_dialog_whole_word_description">عندما تكون الكلمة أو العبارة أبجدية رقمية فقط ، فلن يتم تطبيقها إلا إذا كانت مطابقة للكلمة بأكملها</string> <string name="filter_dialog_whole_word_description">عندما تكون الكلمة أو العبارة أبجدية رقمية فقط ، فلن يتم تطبيقها إلا إذا كانت مطابقة للكلمة بأكملها</string>
<string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string> <string name="poll_info_format"> <!-- 15 votes • 1 hour left --> %1$s • %2$s</string>
</resources> <string name="title_scheduled_toot">التبويقات المبَرمَجة</string>
<string name="action_edit">تعديل</string>
<string name="action_access_scheduled_toot">التبويقات المبَرمَجة</string>
<string name="action_schedule_toot">برمجة تبويق</string>
<string name="action_reset_schedule">صفّر</string>
<string name="hint_configure_scheduled_toot">اضغط هنا لضبط برمجة التبويق.</string>
<string name="post_lookup_error_format">خطأ أثناء البحث عن منشور %s</string>
</resources>

View File

@ -28,7 +28,6 @@
<string name="title_direct_messages">সরাসরি বার্তা</string> <string name="title_direct_messages">সরাসরি বার্তা</string>
<string name="title_tab_preferences">ট্যাবগুলি</string> <string name="title_tab_preferences">ট্যাবগুলি</string>
<string name="title_view_thread">টুট</string> <string name="title_view_thread">টুট</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">পোস্টগুলি</string> <string name="title_statuses">পোস্টগুলি</string>
<string name="title_statuses_with_replies">উত্তরের সাথে</string> <string name="title_statuses_with_replies">উত্তরের সাথে</string>
<string name="title_statuses_pinned">পিন করা</string> <string name="title_statuses_pinned">পিন করা</string>
@ -440,7 +439,6 @@
<string name="poll_info_time_relative">%s বাকি</string> <string name="poll_info_time_relative">%s বাকি</string>
<string name="poll_info_time_absolute">%s এ শেষ হবে</string> <string name="poll_info_time_absolute">%s এ শেষ হবে</string>
<string name="poll_info_closed">বন্ধ</string> <string name="poll_info_closed">বন্ধ</string>
<string name="poll_option_format"> <!-- ১৫% এতে ভোট দিয়েছে! --> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="poll_vote">ভোট</string> <string name="poll_vote">ভোট</string>
@ -505,4 +503,13 @@
<string name="poll_new_choice_hint">পছন্দ %d</string> <string name="poll_new_choice_hint">পছন্দ %d</string>
<string name="edit_poll">সম্পাদন</string> <string name="edit_poll">সম্পাদন</string>
<string name="title_scheduled_toot">নির্ধারিত টুটগুলি</string>
<string name="action_edit">সম্পাদন</string>
<string name="action_access_scheduled_toot">নির্ধারিত টুটগুলি</string>
<string name="action_schedule_toot">নির্ধারিত টুট</string>
<string name="action_reset_schedule">রিসেট</string>
<string name="hint_configure_scheduled_toot">নির্ধারিত টুট কনফিগার করতে এখানে আলতো চাপুন।</string>
<string name="about_powered_by_tusky">টাস্কি দ্বারা চালিত</string>
<string name="post_lookup_error_format">%s পোস্ট অনুসন্ধানে ত্রুটি</string>
</resources> </resources>

View File

@ -23,7 +23,6 @@
<string name="title_public_local">Local</string> <string name="title_public_local">Local</string>
<string name="title_public_federated">Federació</string> <string name="title_public_federated">Federació</string>
<string name="title_view_thread">Toot</string> <string name="title_view_thread">Toot</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">Posts</string> <string name="title_statuses">Posts</string>
<string name="title_follows">Seguits</string> <string name="title_follows">Seguits</string>
<string name="title_followers">Seguidors</string> <string name="title_followers">Seguidors</string>
@ -465,7 +464,6 @@
</plurals> </plurals>
<string name="description_status_cw">Advertència: %s</string> <string name="description_status_cw">Advertència: %s</string>
<string name="poll_option_format"> <!-- 15% vota aquesta acció! --> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="title_statuses_pinned">Toot fixat</string> <string name="title_statuses_pinned">Toot fixat</string>
<string name="unpin_action">Toot no fixat</string> <string name="unpin_action">Toot no fixat</string>

View File

@ -26,7 +26,6 @@
<string name="title_direct_messages">Přímé zprávy</string> <string name="title_direct_messages">Přímé zprávy</string>
<string name="title_tab_preferences">Panely</string> <string name="title_tab_preferences">Panely</string>
<string name="title_view_thread">Toot</string> <string name="title_view_thread">Toot</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">Tooty</string> <string name="title_statuses">Tooty</string>
<string name="title_statuses_with_replies">S odpověďmi</string> <string name="title_statuses_with_replies">S odpověďmi</string>
<string name="title_statuses_pinned">Připnuté</string> <string name="title_statuses_pinned">Připnuté</string>
@ -392,7 +391,6 @@
<string name="poll_info_time_relative">zbývá %s</string> <string name="poll_info_time_relative">zbývá %s</string>
<string name="poll_info_time_absolute">končí v %s</string> <string name="poll_info_time_absolute">končí v %s</string>
<string name="poll_info_closed">uzavřena</string> <string name="poll_info_closed">uzavřena</string>
<string name="poll_option_format"> <!-- 15% vote for this! --> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="poll_vote">Hlasovat</string> <string name="poll_vote">Hlasovat</string>
@ -466,4 +464,19 @@
<string name="poll_new_choice_hint">Možnost %d</string> <string name="poll_new_choice_hint">Možnost %d</string>
<string name="edit_poll">Upravit</string> <string name="edit_poll">Upravit</string>
<string name="title_scheduled_toot">Plánované tooty</string>
<string name="action_edit">Upravit</string>
<string name="action_add_poll">Přidat anketu</string>
<string name="action_access_scheduled_toot">Plánované tooty</string>
<string name="action_schedule_toot">Naplánovat toot</string>
<string name="action_reset_schedule">Obnovit</string>
<string name="hint_configure_scheduled_toot">Klepnutím sem nastavíte plánovaný toot.</string>
<string name="pref_title_alway_open_spoiler">Vždy rozbalovat tooty označené varováními o obsahu</string>
<string name="filter_dialog_whole_word">Celé slovo</string>
<string name="filter_dialog_whole_word_description">Je-li klíčové slovo nebo fráze pouze alfanumerická, bude použita pouze, pokud odpovídá celému slovu</string>
<string name="title_accounts">Účty</string>
<string name="failed_search">Hledání selhalo</string>
<string name="post_lookup_error_format">Chyba při hledání příspěvku %s</string>
</resources> </resources>

View File

@ -278,7 +278,6 @@
<string name="error_network">Digwyddodd gwall rhwydwaith! Gwiriwch eich cysylltiad a cheisiwch eto!</string> <string name="error_network">Digwyddodd gwall rhwydwaith! Gwiriwch eich cysylltiad a cheisiwch eto!</string>
<string name="title_direct_messages">Negeseuon Uniongyrchol</string> <string name="title_direct_messages">Negeseuon Uniongyrchol</string>
<string name="title_tab_preferences">Tabiau</string> <string name="title_tab_preferences">Tabiau</string>
<string name="title_tag">#%s</string>
<string name="title_statuses_pinned">Wedi\'i binio</string> <string name="title_statuses_pinned">Wedi\'i binio</string>
<string name="title_domain_mutes">Parthau cudd</string> <string name="title_domain_mutes">Parthau cudd</string>
<string name="message_empty">Dim byd yma.</string> <string name="message_empty">Dim byd yma.</string>

View File

@ -26,7 +26,6 @@
<string name="title_direct_messages">Direktnachrichten</string> <string name="title_direct_messages">Direktnachrichten</string>
<string name="title_tab_preferences">Tabs</string> <string name="title_tab_preferences">Tabs</string>
<string name="title_view_thread">Unterhaltung</string> <string name="title_view_thread">Unterhaltung</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">Beiträge</string> <string name="title_statuses">Beiträge</string>
<string name="title_statuses_with_replies">mit Antworten</string> <string name="title_statuses_with_replies">mit Antworten</string>
<string name="title_statuses_pinned">Angeheftet</string> <string name="title_statuses_pinned">Angeheftet</string>
@ -376,7 +375,6 @@
<string name="poll_info_time_relative">%s verbleibend</string> <string name="poll_info_time_relative">%s verbleibend</string>
<string name="poll_info_time_absolute">endet um %s</string> <string name="poll_info_time_absolute">endet um %s</string>
<string name="poll_info_closed">Geschlossen</string> <string name="poll_info_closed">Geschlossen</string>
<string name="poll_option_format"> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="poll_vote">Abstimmen</string> <string name="poll_vote">Abstimmen</string>
@ -448,4 +446,11 @@
<string name="add_poll_choice">Auswahlmöglichkeit hinzufügen</string> <string name="add_poll_choice">Auswahlmöglichkeit hinzufügen</string>
<string name="poll_allow_multiple_choices">Mehrere Möglichkeiten</string> <string name="poll_allow_multiple_choices">Mehrere Möglichkeiten</string>
<string name="poll_new_choice_hint">Möglichkeit %d</string> <string name="poll_new_choice_hint">Möglichkeit %d</string>
<string name="title_scheduled_toot">Geplante Beiträge</string>
<string name="action_edit">Editieren</string>
<string name="action_access_scheduled_toot">Geplante Beiträge</string>
<string name="action_schedule_toot">Plane Beitrag</string>
<string name="action_reset_schedule">Zurücksetzen</string>
<string name="hint_configure_scheduled_toot">Drücke hier, um den geplanten Beitrag zu konfigurieren.</string>
<string name="abbreviated_in_years">Dies sind Zeitstempel für Status. Beispiele: \"16s\" oder \"2t\".</string>
</resources> </resources>

View File

@ -26,7 +26,6 @@
<string name="title_direct_messages">Rektaj mesaĝoj</string> <string name="title_direct_messages">Rektaj mesaĝoj</string>
<string name="title_tab_preferences">Langetoj</string> <string name="title_tab_preferences">Langetoj</string>
<string name="title_view_thread">Mesaĝo</string> <string name="title_view_thread">Mesaĝo</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">Mesaĝoj</string> <string name="title_statuses">Mesaĝoj</string>
<string name="title_statuses_with_replies">Kun respondoj</string> <string name="title_statuses_with_replies">Kun respondoj</string>
<string name="title_statuses_pinned">Alpinglitaj</string> <string name="title_statuses_pinned">Alpinglitaj</string>
@ -391,7 +390,6 @@
<string name="poll_info_time_relative">%s restas</string> <string name="poll_info_time_relative">%s restas</string>
<string name="poll_info_time_absolute">finiĝos je %s</string> <string name="poll_info_time_absolute">finiĝos je %s</string>
<string name="poll_info_closed">finiĝita</string> <string name="poll_info_closed">finiĝita</string>
<string name="poll_option_format"> <!-- 15% vote for this! --> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="poll_vote">Voĉdoni</string> <string name="poll_vote">Voĉdoni</string>

View File

@ -18,15 +18,14 @@
<string name="error_media_download_permission">Se requiere permiso para descargar al almacenamiento.</string> <string name="error_media_download_permission">Se requiere permiso para descargar al almacenamiento.</string>
<string name="error_media_upload_image_or_video">No se pueden adjuntar imágenes y vídeos en el mismo estado.</string> <string name="error_media_upload_image_or_video">No se pueden adjuntar imágenes y vídeos en el mismo estado.</string>
<string name="error_media_upload_sending">La subida falló.</string> <string name="error_media_upload_sending">La subida falló.</string>
<string name="error_sender_account_gone">Error al publicar.</string> <string name="error_sender_account_gone">Error al enviar estado.</string>
<string name="title_home">Inicio</string> <string name="title_home">Inicio</string>
<string name="title_notifications">Notificaciones</string> <string name="title_notifications">Notificaciones</string>
<string name="title_public_local">Local</string> <string name="title_public_local">Local</string>
<string name="title_public_federated">Federada</string> <string name="title_public_federated">Federada</string>
<string name="title_direct_messages">Mensajes Directos</string> <string name="title_direct_messages">Mensajes Directos</string>
<string name="title_tab_preferences">Pestañas</string> <string name="title_tab_preferences">Pestañas</string>
<string name="title_view_thread">Publicación</string> <string name="title_view_thread">Estado</string>
<string name="title_tag">#%s</string>
<string name="title_statuses">Estados</string> <string name="title_statuses">Estados</string>
<string name="title_statuses_with_replies">Con respuestas</string> <string name="title_statuses_with_replies">Con respuestas</string>
<string name="title_statuses_pinned">Fijado</string> <string name="title_statuses_pinned">Fijado</string>
@ -50,8 +49,8 @@
<string name="status_content_show_less">Ocultar</string> <string name="status_content_show_less">Ocultar</string>
<string name="message_empty">Nada aquí.</string> <string name="message_empty">Nada aquí.</string>
<string name="footer_empty">Nada por aquí. ¡Arrastra hacia abajo para recargar!</string> <string name="footer_empty">Nada por aquí. ¡Arrastra hacia abajo para recargar!</string>
<string name="notification_reblog_format">%s impulsó tu toot</string> <string name="notification_reblog_format">%s impulsó tu estado</string>
<string name="notification_favourite_format">%s marcó como favorito tu post</string> <string name="notification_favourite_format">%s marcó como favorito tu estado</string>
<string name="notification_follow_format">%s te siguió</string> <string name="notification_follow_format">%s te siguió</string>
<string name="report_username_format">Reportar @%s</string> <string name="report_username_format">Reportar @%s</string>
<string name="report_comment_hint">¿Información adicional?</string> <string name="report_comment_hint">¿Información adicional?</string>
@ -61,7 +60,7 @@
<string name="action_favourite">Favorito</string> <string name="action_favourite">Favorito</string>
<string name="action_more">Más</string> <string name="action_more">Más</string>
<string name="action_compose">Redactar</string> <string name="action_compose">Redactar</string>
<string name="action_login">Iniciar sesión</string> <string name="action_login">Iniciar sesión con Mastodon</string>
<string name="action_logout">Cerrar sesión</string> <string name="action_logout">Cerrar sesión</string>
<string name="action_logout_confirm">¿Seguro que quiere cerrar la sesión de %1$s?</string> <string name="action_logout_confirm">¿Seguro que quiere cerrar la sesión de %1$s?</string>
<string name="action_follow">Seguir</string> <string name="action_follow">Seguir</string>
@ -110,7 +109,7 @@
<string name="action_open_as">Abrir como %s</string> <string name="action_open_as">Abrir como %s</string>
<string name="action_share_as">Compartir como…</string> <string name="action_share_as">Compartir como…</string>
<string name="send_status_link_to">Compartir URL…</string> <string name="send_status_link_to">Compartir URL…</string>
<string name="send_status_content_to">Compartir contenido…</string> <string name="send_status_content_to">Compartir estado…</string>
<string name="send_media_to">Compartir medios a…</string> <string name="send_media_to">Compartir medios a…</string>
<string name="confirmation_reported">¡Enviado!</string> <string name="confirmation_reported">¡Enviado!</string>
<string name="confirmation_unblocked">El usuario ya no está bloqueado</string> <string name="confirmation_unblocked">El usuario ya no está bloqueado</string>
@ -143,7 +142,7 @@
<string name="dialog_download_image">Descargar</string> <string name="dialog_download_image">Descargar</string>
<string name="dialog_message_cancel_follow_request">¿Cancelar petición de amistad?</string> <string name="dialog_message_cancel_follow_request">¿Cancelar petición de amistad?</string>
<string name="dialog_unfollow_warning">¿Dejar de seguir esta cuenta?</string> <string name="dialog_unfollow_warning">¿Dejar de seguir esta cuenta?</string>
<string name="dialog_delete_toot_warning">¿Eliminar este toot?</string> <string name="dialog_delete_toot_warning">¿Eliminar este estado\?</string>
<string name="visibility_public">Público: Mostrar en historias públicas</string> <string name="visibility_public">Público: Mostrar en historias públicas</string>
<string name="visibility_unlisted">Oculto: No mostrar en historias públicas</string> <string name="visibility_unlisted">Oculto: No mostrar en historias públicas</string>
<string name="visibility_private">Privado: Sólo visible para seguidores</string> <string name="visibility_private">Privado: Sólo visible para seguidores</string>
@ -260,8 +259,8 @@
<string name="lock_account_label_description">Tendrá que admitir los seguidores manualmente</string> <string name="lock_account_label_description">Tendrá que admitir los seguidores manualmente</string>
<string name="compose_save_draft">¿Guardar borrador?</string> <string name="compose_save_draft">¿Guardar borrador?</string>
<string name="send_toot_notification_title">Enviando estado…</string> <string name="send_toot_notification_title">Enviando estado…</string>
<string name="send_toot_notification_error_title">Error enviando estado</string> <string name="send_toot_notification_error_title">Error al enviar el estado</string>
<string name="send_toot_notification_channel_name">Enviando estado</string> <string name="send_toot_notification_channel_name">Enviando estados</string>
<string name="send_toot_notification_cancel_title">Envío cancelado</string> <string name="send_toot_notification_cancel_title">Envío cancelado</string>
<string name="send_toot_notification_saved_content">Una copia del estado se ha guardado en borradores</string> <string name="send_toot_notification_saved_content">Una copia del estado se ha guardado en borradores</string>
<string name="action_compose_shortcut">Redactar</string> <string name="action_compose_shortcut">Redactar</string>
@ -370,7 +369,7 @@
<string name="downloading_media">Descargando contenido</string> <string name="downloading_media">Descargando contenido</string>
<string name="dialog_redraft_toot_warning">¿Borrar y devolver a borradores este toot\?</string> <string name="dialog_redraft_toot_warning">¿Borrar y devolver a borradores este estado\?</string>
<string name="pref_title_notification_filter_poll">encuestas que han terminado</string> <string name="pref_title_notification_filter_poll">encuestas que han terminado</string>
<string name="notification_poll_description">Notificaciones sobre encuestas que han terminado</string> <string name="notification_poll_description">Notificaciones sobre encuestas que han terminado</string>
@ -416,8 +415,6 @@
<string name="action_open_reblogger">Abrir autor del impulso</string> <string name="action_open_reblogger">Abrir autor del impulso</string>
<string name="action_open_reblogged_by">Mostrar impulsos</string> <string name="action_open_reblogged_by">Mostrar impulsos</string>
<string name="poll_option_format"> <!-- 15% vote for this! --> &lt;b&gt;%1$d%%&lt;/b&gt; %2$s</string>
<string name="title_domain_mutes">Dominios ocultos</string> <string name="title_domain_mutes">Dominios ocultos</string>
<string name="action_view_domain_mutes">Dominios ocultos</string> <string name="action_view_domain_mutes">Dominios ocultos</string>
<string name="action_mute_domain">Silenciar %s</string> <string name="action_mute_domain">Silenciar %s</string>
@ -445,7 +442,7 @@
<string name="report_description_remote_instance">La cuenta es de otro servidor. ¿Enviar una copia anónima del reporte\?</string> <string name="report_description_remote_instance">La cuenta es de otro servidor. ¿Enviar una copia anónima del reporte\?</string>
<string name="pref_title_show_notifications_filter">Mostrar filtro de notificaciones</string> <string name="pref_title_show_notifications_filter">Mostrar filtro de notificaciones</string>
<string name="pref_title_alway_open_spoiler">Mostrar siempre toots marcados con avisos de contenido</string> <string name="pref_title_alway_open_spoiler">Mostrar siempre estados marcados con avisos de contenido</string>
<string name="title_accounts">Cuentas</string> <string name="title_accounts">Cuentas</string>
<string name="failed_search">Error al buscar</string> <string name="failed_search">Error al buscar</string>
@ -463,4 +460,13 @@
<string name="poll_new_choice_hint">Opción %d</string> <string name="poll_new_choice_hint">Opción %d</string>
<string name="edit_poll">Editar</string> <string name="edit_poll">Editar</string>
</resources> <string name="title_scheduled_toot">Estados programados</string>
<string name="action_edit">Editar</string>
<string name="action_access_scheduled_toot">Estados programados</string>
<string name="action_schedule_toot">Programar estado</string>
<string name="action_reset_schedule">Reiniciar</string>
<string name="hint_configure_scheduled_toot">Pulsa aquí para configurar un estado programado.</string>
<string name="post_lookup_error_format">Error al buscar el post %s</string>
<string name="about_powered_by_tusky">Potenciado por Tusky</string>
</resources>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version='1.0' encoding='UTF-8'?>
<resources> <resources>
<string name="error_generic">Errorea gertatu da.</string> <string name="error_generic">Errorea gertatu da.</string>
<string name="error_empty">Eremu hau ezin da hutsik egon.</string> <string name="error_empty">Eremu hau ezin da hutsik egon.</string>
<string name="error_invalid_domain">Domeinu izen okerra.</string> <string name="error_invalid_domain">Domeinu baliogabea sartu da</string>
<string name="error_failed_app_registration">Akatsa saioa hasterakoan.</string> <string name="error_failed_app_registration">Akatsa saioa hasterakoan.</string>
<string name="error_no_web_browser_found">Ez da web nabigatzailerik aurkitu.</string> <string name="error_no_web_browser_found">Ez da web nabigatzailerik aurkitu.</string>
<string name="error_authorization_unknown">Identifikatu gabeko baimentza akatsa gertatu da.</string> <string name="error_authorization_unknown">Identifikatu gabeko baimentza akatsa gertatu da.</string>
@ -27,7 +27,7 @@
<string name="title_statuses_with_replies">Erantzunekin</string> <string name="title_statuses_with_replies">Erantzunekin</string>
<string name="title_follows">Jarraitzen</string> <string name="title_follows">Jarraitzen</string>
<string name="title_followers">Jarraitzaileak</string> <string name="title_followers">Jarraitzaileak</string>
<string name="title_favourites">Gogokoak</string> <string name="title_favourites">Gogokoenak</string>
<string name="title_mutes">Isilduak</string> <string name="title_mutes">Isilduak</string>
<string name="title_blocks">Blokeatuak</string> <string name="title_blocks">Blokeatuak</string>
<string name="title_follow_requests">Eskakizunak</string> <string name="title_follow_requests">Eskakizunak</string>
@ -99,22 +99,22 @@
<string name="action_emoji_keyboard">Emoji teklatua</string> <string name="action_emoji_keyboard">Emoji teklatua</string>
<string name="download_image">%1$s jaisten</string> <string name="download_image">%1$s jaisten</string>
<string name="action_copy_link">Lotura kopiatu</string> <string name="action_copy_link">Lotura kopiatu</string>
<string name="send_status_link_to">Tutaren URL-a partekatu…</string> <string name="send_status_link_to">Tutaren URLa partekatu…</string>
<string name="send_status_content_to">Tuta partekatu…</string> <string name="send_status_content_to">Tuta partekatu…</string>
<string name="send_media_to">Partekatu multimedia hona…</string> <string name="send_media_to">Partekatu media hona…</string>
<string name="confirmation_reported">Bidalia!</string> <string name="confirmation_reported">Bidalia!</string>
<string name="confirmation_unblocked">Erabiltzailea ez dago blokeatuta iada.</string> <string name="confirmation_unblocked">Erabiltzailea desblokeatuta</string>
<string name="confirmation_unmuted">Iada erabiltzailea ez dago mutututa.</string> <string name="confirmation_unmuted">Erabiltzailea isil gabetua</string>
<string name="status_sent">Bidalia!</string> <string name="status_sent">Bidalia!</string>
<string name="status_sent_long">Erantzuna ongi bidali da.</string> <string name="status_sent_long">Erantzuna ongi bidali da.</string>
<string name="hint_domain">Instantzia hautatu</string> <string name="hint_domain">Zein instantzia\?</string>
<string name="hint_compose">Zer duzu buruan?</string> <string name="hint_compose">Zer duzu buruan?</string>
<string name="hint_content_warning">Eduki abisua</string> <string name="hint_content_warning">Eduki-abisua</string>
<string name="hint_display_name">Izena</string> <string name="hint_display_name">Agertuko den izena</string>
<string name="hint_note">Biografia</string> <string name="hint_note">Biografia</string>
<string name="hint_search">Bilatu…</string> <string name="hint_search">Bilatu…</string>
<string name="search_no_results">Emaitzarik ez</string> <string name="search_no_results">Emaitzarik ez</string>
<string name="label_quick_reply">Erantzuna</string> <string name="label_quick_reply">Erantzun…</string>
<string name="label_avatar">Irudia</string> <string name="label_avatar">Irudia</string>
<string name="label_header">Goiburua</string> <string name="label_header">Goiburua</string>
<string name="link_whats_an_instance">Zer da instantzia?</string> <string name="link_whats_an_instance">Zer da instantzia?</string>
@ -124,12 +124,12 @@
\n\nInstantzia zure kontua dagoen gunea da, baino beste instantzietako erabiltzaileak zurean egongo balira bezala jarraitu ditzakezu. \n\nInstantzia zure kontua dagoen gunea da, baino beste instantzietako erabiltzaileak zurean egongo balira bezala jarraitu ditzakezu.
\n\nInformazio gehiago <a href="https://joinmastodon.org">joinmastodon.org</a> helbidean topatuko duzu. \n\nInformazio gehiago <a href="https://joinmastodon.org">joinmastodon.org</a> helbidean topatuko duzu.
</string> </string>
<string name="dialog_title_finishing_media_upload">Multimedia igoera bukatzen</string> <string name="dialog_title_finishing_media_upload">Mediaren igoera bukatzen</string>
<string name="dialog_message_uploading_media">Igotzen…</string> <string name="dialog_message_uploading_media">Igotzen…</string>
<string name="dialog_download_image">Jaitsi</string> <string name="dialog_download_image">Jaitsi</string>
<string name="dialog_message_cancel_follow_request">Lagun eskaera ezeztatu?</string> <string name="dialog_message_cancel_follow_request">Jarraipen-eskaerari uko egin\?</string>
<string name="dialog_unfollow_warning">Kontu hau jarraitzeari utzi?</string> <string name="dialog_unfollow_warning">Kontu honi jarraitzeari utzi\?</string>
<string name="dialog_delete_toot_warning">Tuta ezabatu?</string> <string name="dialog_delete_toot_warning">Tut hau ezabatu\?</string>
<string name="visibility_public">Publikoa: Istorio publikoetan erakutsi</string> <string name="visibility_public">Publikoa: Istorio publikoetan erakutsi</string>
<string name="visibility_unlisted">Ezkutukoa: Ez erakutsi istorio publikoetan</string> <string name="visibility_unlisted">Ezkutukoa: Ez erakutsi istorio publikoetan</string>
<string name="visibility_private">Pribatua: Jarraitzaileentzat soilik ikusgai</string> <string name="visibility_private">Pribatua: Jarraitzaileentzat soilik ikusgai</string>
@ -140,53 +140,53 @@
<string name="pref_title_notification_alert_sound">Soinuarekin jakinarazi</string> <string name="pref_title_notification_alert_sound">Soinuarekin jakinarazi</string>
<string name="pref_title_notification_alert_vibrate">Bibrazioarekin jakinarazi</string> <string name="pref_title_notification_alert_vibrate">Bibrazioarekin jakinarazi</string>
<string name="pref_title_notification_alert_light">Led-arekin jakinarazi</string> <string name="pref_title_notification_alert_light">Led-arekin jakinarazi</string>
<string name="pref_title_notification_filters">Noiz jakinarazi:</string> <string name="pref_title_notification_filters">Noiz jakinarazi</string>
<string name="pref_title_notification_filter_mentions">Aipatzen naute</string> <string name="pref_title_notification_filter_mentions">Aipatzen naute</string>
<string name="pref_title_notification_filter_follows">Jarraitzen didate</string> <string name="pref_title_notification_filter_follows">Jarraitzen didate</string>
<string name="pref_title_notification_filter_reblogs">Bultzatzen naute</string> <string name="pref_title_notification_filter_reblogs">Bultzatzen naute</string>
<string name="pref_title_notification_filter_favourites">Nire mezuak gogoko ditu</string> <string name="pref_title_notification_filter_favourites">Nire argitarapenak gustokoak izan dira</string>
<string name="pref_title_appearance_settings">Interfazea</string> <string name="pref_title_appearance_settings">Interfazea</string>
<string name="pref_title_app_theme">Gaia</string> <string name="pref_title_app_theme">Aplikazioaren gaia</string>
<string name="pref_title_timelines">Denbora lerroak</string> <string name="pref_title_timelines">Denbora-lerroak</string>
<string name="app_them_dark">Iluna</string> <string name="app_them_dark">Iluna</string>
<string name="app_theme_light">Argia</string> <string name="app_theme_light">Argia</string>
<string name="app_theme_black">Beltza</string> <string name="app_theme_black">Beltza</string>
<string name="app_theme_auto">Automatikoa</string> <string name="app_theme_auto">Automatikoa iluntzean</string>
<string name="pref_title_browser_settings">Nabigatzailea</string> <string name="pref_title_browser_settings">Nabigatzailea</string>
<string name="pref_title_custom_tabs">Chromeko fitxak erabili</string> <string name="pref_title_custom_tabs">Chromeko fitxa pertsonalizatuak erabili</string>
<string name="pref_title_hide_follow_button">Tut egiteko botoia ezkutatu jaisterakoan.</string> <string name="pref_title_hide_follow_button">Ezkutatu idazteko botoia mugitzean</string>
<string name="pref_title_status_filter">Denbora-lerro filtroak</string> <string name="pref_title_status_filter">Denbora-lerroko iragazkiak</string>
<string name="pref_title_status_tabs">Fitxak</string> <string name="pref_title_status_tabs">Fitxak</string>
<string name="pref_title_show_boosts">Erakutsi bultzadak</string> <string name="pref_title_show_boosts">Erakutsi bultzadak</string>
<string name="pref_title_show_replies">Erakutsi erantzunak</string> <string name="pref_title_show_replies">Erakutsi erantzunak</string>
<string name="pref_title_show_media_preview">Multimedia aurreikusi</string> <string name="pref_title_show_media_preview">Jaitsi mediaren aurreikuspenak</string>
<string name="pref_title_proxy_settings">Proxy-a</string> <string name="pref_title_proxy_settings">Proxya</string>
<string name="pref_title_http_proxy_settings">HTTP Proxy-a</string> <string name="pref_title_http_proxy_settings">HTTP proxya</string>
<string name="pref_title_http_proxy_enable">HTTP Proxy-a gaitu</string> <string name="pref_title_http_proxy_enable">HTTP proxya gaitu</string>
<string name="pref_title_http_proxy_server">HTTP Proxy-aren zerbitzaria</string> <string name="pref_title_http_proxy_server">HTTP proxyaren zerbitzaria</string>
<string name="pref_title_http_proxy_port">HTTP Proxy-aren portua</string> <string name="pref_title_http_proxy_port">HTTP proxyaren portua</string>
<string name="pref_default_post_privacy">Aurrezarritako ikusgarritasuna</string> <string name="pref_default_post_privacy">Argitarapenen aurrezarritako pribatutasuna</string>
<string name="pref_default_media_sensitivity">Beti markatu multimedia eduki mingarri gisa</string> <string name="pref_default_media_sensitivity">Media eduki-mingarri gisa beti markatu</string>
<string name="pref_publishing">Bidalketak</string> <string name="pref_publishing">Argitaratzeak (zerbitzariarekin sinkronizatua)</string>
<string name="pref_failed_to_sync">Aukerak sinkronizatzean akatsa</string> <string name="pref_failed_to_sync">Aukerak sinkronizatzean akatsa</string>
<string name="post_privacy_public">Publikoa</string> <string name="post_privacy_public">Publikoa</string>
<string name="post_privacy_unlisted">Ezkutukoa</string> <string name="post_privacy_unlisted">Zerrendatu gabea</string>
<string name="post_privacy_followers_only">Pribatua</string> <string name="post_privacy_followers_only">Jarraitzaileak soilik</string>
<string name="pref_status_text_size">Testuaren tamaina</string> <string name="pref_status_text_size">Status testuaren tamaina</string>
<string name="status_text_size_smallest">Oso txikia</string> <string name="status_text_size_smallest">Txikiena</string>
<string name="status_text_size_small">Txikia</string> <string name="status_text_size_small">Txikia</string>
<string name="status_text_size_medium">Erdikoa</string> <string name="status_text_size_medium">Erdikoa</string>
<string name="status_text_size_large">Handia</string> <string name="status_text_size_large">Handia</string>
<string name="status_text_size_largest">Oso handia</string> <string name="status_text_size_largest">Handiena</string>
<string name="notification_mention_name">Aipamen berriak</string> <string name="notification_mention_name">Aipamen berriak</string>
<string name="notification_mention_descriptions">Aipamen berrien jakinarazpenak</string> <string name="notification_mention_descriptions">Aipamen berrien jakinarazpenak</string>
<string name="notification_follow_name">Jarritzaile berriak</string> <string name="notification_follow_name">Jarraitzaile berriak</string>
<string name="notification_follow_description">Jarraitzaile berrien jakinarazpenak</string> <string name="notification_follow_description">Jarraitzaile berrien jakinarazpenak</string>
<string name="notification_boost_name">Bultzadak</string> <string name="notification_boost_name">Bultzadak</string>
<string name="notification_boost_description">Bultzatutako tuten jakinarazpenak</string> <string name="notification_boost_description">Bultzatutako zure tuten jakinarazpenak</string>
<string name="notification_favourite_name">Gogokoak</string> <string name="notification_favourite_name">Gogokoak</string>
<string name="notification_favourite_description">Gogokoen jakinarazpenak</string> <string name="notification_favourite_description">Zure tutak gogoko bezala ezartzerakoan jakinarazpenak</string>
<string name="notification_mention_format">%s-k aipatu zaitu</string> <string name="notification_mention_format">%s-(e)k aipatu zaitu</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s eta beste %4$d</string> <string name="notification_summary_large">%1$s, %2$s, %3$s eta beste %4$d</string>
<string name="notification_summary_medium">%1$s, %2$s eta %3$s</string> <string name="notification_summary_medium">%1$s, %2$s eta %3$s</string>
<string name="notification_summary_small">%1$s eta %2$s</string> <string name="notification_summary_small">%1$s eta %2$s</string>
@ -205,12 +205,11 @@
<string name="about_project_site"> Proiektuaren gunea:\n <string name="about_project_site"> Proiektuaren gunea:\n
https://accelf.net/yuito https://accelf.net/yuito
</string> </string>
<string name="about_bug_feature_request_site"> Akats jakinarazpenak eta hobekuntza eskariak:\n <string name="about_bug_feature_request_site">Akatsen berri-emateak eta hobekuntza-eskariak:
https://github.com/accelforce/Yuito/issues \n https://github.com/accelforce/Yuito/issues</string>
</string> <string name="about_tusky_account">Yuitoren profila</string>
<string name="about_tusky_account">Yuito-ren profila</string> <string name="status_share_content">Partekatu tutaren edukia</string>
<string name="status_share_content">Partekatu edukia</string> <string name="status_share_link">Partekatu tutaren lotura</string>
<string name="status_share_link">Partekatu lotura</string>
<string name="status_media_images">Irudiak</string> <string name="status_media_images">Irudiak</string>
<string name="status_media_video">Bideoak</string> <string name="status_media_video">Bideoak</string>
<string name="state_follow_requested">Eskaera bidalita</string> <string name="state_follow_requested">Eskaera bidalita</string>
@ -277,4 +276,196 @@
<string name="label_remote_account">Ondorengo edukiak erabiltzailearen informazioa erdizka erakutsi dezake. Profil osoa ikusteko nabigatzailean sakatu.</string> <string name="label_remote_account">Ondorengo edukiak erabiltzailearen informazioa erdizka erakutsi dezake. Profil osoa ikusteko nabigatzailean sakatu.</string>
<string name="unpin_action">Desainguratu</string> <string name="unpin_action">Desainguratu</string>
<string name="pin_action">Ainguratu</string> <string name="pin_action">Ainguratu</string>
<string name="error_network">Sareko errore bat sortu da! Zure konexioa ziurta ezazu berriro, mesedez!</string>
<string name="title_direct_messages">Mezu Zuzenak</string>
<string name="title_tab_preferences">Kategoriak</string>
<string name="title_statuses_pinned">Lotuta</string>
<string name="title_domain_mutes">Ezkutuko domeinuak</string>
<string name="title_scheduled_toot">Programatutako tutak</string>
<string name="status_username_format">\@%s</string>
<string name="message_empty">Kilkerrak besterik ez hemen.</string>
<string name="action_unreblog">Bultzada kendu</string>
<string name="action_unfavourite">Gogokoa kendu</string>
<string name="action_edit">Editatu</string>
<string name="action_delete_and_redraft">Ezabatu eta zirriborroa berriro egin</string>
<string name="action_view_domain_mutes">Ezkutuko domeinuak</string>
<string name="action_add_poll">Galdeketa gehitu</string>
<string name="action_mute_domain">%s isilarazi</string>
<string name="action_access_scheduled_toot">Programatutako tutak</string>
<string name="action_schedule_toot">Tuta programatu</string>
<string name="action_reset_schedule">Berrezarri</string>
<string name="action_add_tab">Kategoria gehitu</string>
<string name="action_links">Estekak</string>
<string name="action_mentions">Aipamenak</string>
<string name="action_open_reblogged_by">Bultzadak erakutsi</string>
<string name="action_open_faved_by">Gogokoak erakutsi</string>
<string name="title_mentions_dialog">Aipamenak</string>
<string name="title_links_dialog">Estekak</string>
<string name="action_open_media_n">Ireki media #%d</string>
<string name="action_open_as">%s bezala ireki</string>
<string name="action_share_as">... bezala partekatu</string>
<string name="download_media">Media jaisten</string>
<string name="downloading_media">Media jaisten</string>
<string name="confirmation_domain_unmuted">%s ez dago ezkutatua</string>
<string name="hint_configure_scheduled_toot">Sakatu hemen programatutako tuta konfiguratzeko.</string>
<string name="dialog_redraft_toot_warning">Tut hau ezabatu eta zirriborro berria egin\?</string>
<string name="mute_domain_warning">Ziur al zaude %s ezabatu nahi duzula\? Domeinu horretatik datorren edukia ez duzu denbora-lerro publikoetan edo jakinarazpenentan ikusiko. Domeinu horretan dituzun jarraitzaileak ezabatuko dira.</string>
<string name="mute_domain_warning_dialog_ok">Domeinu osoa ezkutatu</string>
<string name="pref_title_notification_filter_poll">Galdeketak bukatu dira</string>
<string name="pref_title_timeline_filters">Iragazkiak</string>
<string name="app_theme_system">Erabili sistemaren diseinua</string>
<string name="pref_title_language">Hizkuntza</string>
<string name="pref_title_bot_overlay">Botentzako erakuslea erakutsi</string>
<string name="pref_title_animate_gif_avatars">GIF abatarrak animatu</string>
<string name="notification_poll_name">Galdeketak</string>
<string name="notification_poll_description">Bukatutako galdeketen jakinarazpenak</string>
<string name="about_tusky_version">Tusky %s</string>
<string name="action_hashtags">Traolak</string>
<string name="title_hashtags_dialog">Traolak</string>
<string name="about_powered_by_tusky">Tusky-k sustatuta</string>
<string name="abbreviated_hours_ago">%dh</string>
<string name="abbreviated_minutes_ago">%dm</string>
<string name="abbreviated_seconds_ago">%ds</string>
<string name="pref_title_alway_open_spoiler">Beti zabaldu edukien abisuekin markatutako tootak</string>
<string name="pref_title_thread_filter_keywords">Elkarrizketak</string>
<string name="filter_addition_dialog_title">Gehitu iragazkia</string>
<string name="filter_edit_dialog_title">Editatu iragazkia</string>
<string name="filter_dialog_remove_button">Kendu</string>
<string name="filter_dialog_update_button">Eguneratu</string>
<string name="filter_dialog_whole_word">Hitz osoa</string>
<string name="filter_dialog_whole_word_description">Gako-hitza edo esaldia alfanumerikoa denean bakarrik, hitz osoarekin bat datorrenean bakarrik aplikatuko da</string>
<string name="filter_add_description">Iragazteko esaldia</string>
<string name="error_create_list">Ezin izan da zerrenda sortu</string>
<string name="error_rename_list">Ezin izan da zerrendaren izena aldatu</string>
<string name="error_delete_list">Ezin izan da zerrenda ezabatu</string>
<string name="action_create_list">Zerrenda sortu</string>
<string name="action_rename_list">Zerrenda berrizendatu</string>
<string name="action_delete_list">Ezabatu zerrenda</string>
<string name="action_edit_list">Editatu zerrenda</string>
<string name="hint_search_people_list">Bilatu jarraitzen dituzun pertsonak</string>
<string name="action_add_to_list">Gehitu kontua zerrendan</string>
<string name="action_remove_from_list">Kendu kontua zerrendatik</string>
<string name="caption_notoemoji">Google-ren egungo emoji multzoa</string>
<string name="license_cc_by_4">CC-BY 4.0</string>
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
<plurals name="favs">
<item quantity="one"><b>%1$s</b> Gogoko</item>
<item quantity="other"><b>%1$s</b> Gogoko</item>
</plurals>
<plurals name="reblogs">
<item quantity="one"><b>%s</b> Bultzada</item>
<item quantity="other"><b>%s</b> Bultzada</item>
</plurals>
<string name="title_reblogged_by">Bultzatuta</string>
<string name="title_favourited_by">Gogokoa</string>
<string name="conversation_1_recipients">%1$s</string>
<string name="conversation_2_recipients">%1$s eta %2$s</string>
<string name="conversation_more_recipients">%1$s, %2$s eta %3$d gehiago</string>
<string name="max_tab_number_reached">geienezko %1$d fitxa iritsita</string>
<string name="description_status_media">Media: %s</string>
<string name="description_status_cw">Edukiaren abisua: %s</string>
<string name="description_status_media_no_description_placeholder">Deskribapenik ez</string>
<string name="description_status_reblogged">Birblogeatuta</string>
<string name="description_status_favourited">Gogotuta</string>
<string name="description_visiblity_public">Publiko</string>
<string name="description_visiblity_unlisted">Zerrendagabetuta</string>
<string name="description_visiblity_private">Jarraitzaileak</string>
<string name="description_visiblity_direct">Zuzena</string>
<string name="description_poll">Inkestatu aukerekin: %1$s, %2$s, %3$s, %4$s; %5$s</string>
<string name="hint_list_name">Zerrendaren izena</string>
<string name="edit_hashtag_title">Editatu traola</string>
<string name="edit_hashtag_hint">Traola # gabe</string>
<string name="hashtag">Traola</string>
<string name="notifications_clear">Garbitu</string>
<string name="notifications_apply_filter">Iragazi</string>
<string name="filter_apply">Aplikatu</string>
<string name="compose_shortcut_long_label">Idatzi Toot-a</string>
<string name="compose_shortcut_short_label">Idatzi</string>
<string name="notification_clear_text">Ziur zaude jakinarazpen guztiak betirako garbitu nahi dituzula\?</string>
<string name="compose_preview_image_description">%s irudiarentzako ekintzak</string>
<string name="poll_info_format"> <!-- 15 boto • Ordu 1 geratzen da --> %1$s • %2$s</string>
<plurals name="poll_info_votes">
<item quantity="one">Boto %s</item>
<item quantity="other">%s Boto</item>
</plurals>
<string name="poll_info_time_absolute">%s amaitzen da</string>
<string name="poll_info_closed">itxita</string>
<string name="poll_vote">Botatu</string>
<string name="poll_ended_voted">Botoa eman duzun galdeketa amaitu da</string>
<string name="poll_ended_created">Sortu duzun galdeketa amaitu da</string>
<plurals name="poll_timespan_days">
<item quantity="one">Egun %d</item>
<item quantity="other">%d egun</item>
</plurals>
<plurals name="poll_timespan_hours">
<item quantity="one">Ordu %d</item>
<item quantity="other">%d ordu</item>
</plurals>
<plurals name="poll_timespan_minutes">
<item quantity="one">Minutu %d</item>
<item quantity="other">%d minutu</item>
</plurals>
<plurals name="poll_timespan_seconds">
<item quantity="one">Segundu %d</item>
<item quantity="other">%d segundu</item>
</plurals>
<string name="button_continue">Jarraitu</string>
<string name="button_back">Itzuli</string>
<string name="button_done">Eginda</string>
<string name="report_sent_success">\@%s jakinarazi duzu arrakastaz</string>
<string name="hint_additional_info">Iruzkin gehigarriak</string>
<string name="report_remote_instance">%s-(r)i birbidali</string>
<string name="failed_report">Txostena huts egin du</string>
<string name="failed_fetch_statuses">Egoeren eskuratzea huts egin du</string>
<string name="report_description_1">Txostena zure zerbitzariaren moderatzaileari bidaliko zaio. Jarraian, kontu honen zergatia salatzen duzun azalpena eman dezakezu:</string>
<string name="report_description_remote_instance">Kontua beste zerbitzari batekoa da. Bidali txostenaren kopia anonimatua hara ere\?</string>
<string name="title_accounts">Kontuak</string>
<string name="failed_search">Bilaketa huts egin du</string>
<string name="pref_title_show_notifications_filter">Erakutsi jakinarazpenen iragazkia</string>
<string name="create_poll_title">Inkesta</string>
<string name="poll_duration_5_min">5 minutu</string>
<string name="poll_duration_30_min">30 minutu</string>
<string name="poll_duration_1_hour">Ordu 1</string>
<string name="poll_duration_6_hours">6 ordu</string>
<string name="poll_duration_1_day">Egun 1</string>
<string name="poll_duration_3_days">3 egun</string>
<string name="poll_duration_7_days">7 egun</string>
<string name="add_poll_choice">Gehitu aukera</string>
<string name="poll_allow_multiple_choices">Aukera anitzak</string>
<string name="poll_new_choice_hint">%d. aukera</string>
<string name="edit_poll">Editatu</string>
<string name="post_lookup_error_format">Errorea agertu da %s mezua bilatzean</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show More