diff --git a/README.md b/README.md index 1be6dfd85..945aca772 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Yuito is fork of [Tusky](https://github.com/tuskyapp/Tusky). -Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly. +Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is an ActivityPub federated social network. That means no single entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly. ## Features @@ -22,6 +22,8 @@ Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/ If you have any bug reports, feature requests or questions please open an issue or send us a toot at [@ars42525@odakyu.app](https://odakyu.app/@ars42525)! +For translating Tusky into your language, visit https://weblate.tusky.app/ + ### Head of development This app was developed by [@ars42525@odakyu.app](https://odakyu.app/@ars42525). diff --git a/app/build.gradle b/app/build.gradle index d0e3991ac..55c59520c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,9 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -def getGitSha = { -> +apply from: "../instance-build.gradle" + +def getGitSha = { def stdout = new ByteArrayOutputStream() exec { commandLine 'git', 'rev-parse', '--short', 'HEAD' @@ -13,19 +15,26 @@ def getGitSha = { -> } android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { applicationId 'net.accelf.yuito' minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 4 versionName '1.1.2' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + resValue "string", "app_name", APP_NAME + + buildConfigField("String", "CUSTOM_LOGO_URL", "\"$CUSTOM_LOGO_URL\"") + buildConfigField("String", "CUSTOM_INSTANCE", "\"$CUSTOM_INSTANCE\"") + buildConfigField("String", "SUPPORT_ACCOUNT_URL", "\"$SUPPORT_ACCOUNT_URL\"") + kapt { arguments { arg("room.schemaLocation", "$projectDir/schemas") + arg("room.incremental", "true") } } } @@ -61,9 +70,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - androidExtensions { - experimental = true - } testOptions { unitTests { returnDefaultValues = true @@ -74,7 +80,6 @@ android { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } - packagingOptions { // Exclude unneeded files added by libraries exclude 'LICENSE_OFL' @@ -94,8 +99,11 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } } -ext.daggerVersion = '2.24' +ext.roomVersion = '2.2.1' ext.retrofitVersion = '2.6.0' +ext.okhttpVersion = '4.2.2' +ext.glideVersion = '4.10.0' +ext.daggerVersion = '2.25.2' repositories { maven { @@ -105,72 +113,84 @@ repositories { // if libraries are changed here, they should also be changed in LicenseActivity dependencies { - implementation('com.mikepenz:materialdrawer:6.1.2@aar') { - transitive = true - } - implementation 'androidx.core:core:1.0.2' - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.browser:browser:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'androidx.legacy:legacy-support-v13:1.0.0' - implementation 'com.google.android.material:material:1.1.0-alpha10' - implementation 'androidx.exifinterface:exifinterface:1.0.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.preference:preference:1.1.0-alpha04' + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + implementation "androidx.core:core-ktx:1.2.0-beta01" + implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.browser:browser:1.0.0" + implementation "androidx.recyclerview:recyclerview:1.0.0" + implementation "androidx.exifinterface:exifinterface:1.0.0" + implementation "androidx.cardview:cardview:1.0.0" + implementation "androidx.preference:preference:1.1.0" + implementation "androidx.sharetarget:sharetarget:1.0.0-beta01" + implementation "androidx.emoji:emoji:1.0.0" + implementation "androidx.emoji:emoji-appcompat:1.0.0" + implementation "androidx.lifecycle:lifecycle-extensions:2.1.0" + implementation "androidx.constraintlayout:constraintlayout:1.1.3" + implementation "androidx.paging:paging-runtime-ktx:2.1.0" + implementation "androidx.viewpager2:viewpager2:1.0.0-rc01" + implementation "androidx.room:room-runtime:$roomVersion" + implementation "androidx.room:room-rxjava2:$roomVersion" + kapt "androidx.room:room-compiler:$roomVersion" + + implementation "com.google.android.material:material:1.1.0-beta01" + implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" - implementation 'com.squareup.okhttp3:okhttp:4.2.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0' - implementation 'org.conscrypt:conscrypt-android:2.2.1' - implementation 'com.github.connyduck:sparkbutton:2.0.1' - implementation 'com.github.chrisbanes:PhotoView:2.3.0' - implementation 'com.mikepenz:google-material-typeface:3.0.1.3.original@aar' - implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') { - exclude group: 'com.android.support' - } - implementation 'com.evernote:android-job:1.2.6' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - // EmojiCompat - implementation 'androidx.emoji:emoji:1.0.0' - implementation 'androidx.emoji:emoji-appcompat:1.0.0' - implementation 'de.c1710:filemojicompat:1.0.17' - // architecture components - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' - //room - implementation 'androidx.room:room-runtime:2.1.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - kapt 'androidx.room:room-compiler:2.1.0' - implementation 'androidx.room:room-rxjava2:2.1.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" + implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion" + + implementation "org.conscrypt:conscrypt-android:2.2.1" + + implementation "com.github.bumptech.glide:glide:$glideVersion" + implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion" + + implementation "io.reactivex.rxjava2:rxjava:2.2.13" + implementation "io.reactivex.rxjava2:rxandroid:2.1.1" + implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" + + implementation "com.uber.autodispose:autodispose-android-archcomponents:1.4.0" + implementation "com.uber.autodispose:autodispose:1.4.0" + implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" implementation "com.google.dagger:dagger-android:$daggerVersion" implementation "com.google.dagger:dagger-android-support:$daggerVersion" kapt "com.google.dagger:dagger-android-processor:$daggerVersion" - testImplementation 'org.robolectric:robolectric:4.3' - testImplementation 'org.mockito:mockito-inline:3.0.0' - testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0' - androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', { - exclude group: 'com.android.support', module: 'support-annotations' + + implementation "com.github.connyduck:sparkbutton:2.0.1" + + implementation "com.github.chrisbanes:PhotoView:2.3.0" + + implementation("com.mikepenz:materialdrawer:6.1.2@aar") { + transitive = true + } + implementation "com.mikepenz:google-material-typeface:3.0.1.3.original@aar" + + implementation("com.theartofdev.edmodo:android-image-cropper:2.8.0") { + exclude group: "com.android.support" + } + + implementation "com.evernote:android-job:1.4.2" + + implementation "de.c1710:filemojicompat:1.0.17" + + + testImplementation "androidx.test.ext:junit:1.1.1" + testImplementation "org.robolectric:robolectric:4.3.1" + testImplementation "org.mockito:mockito-inline:3.1.0" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + + androidTestImplementation("androidx.test.espresso:espresso-core:3.1.1", { + exclude group: "com.android.support", module: "support-annotations" }) - androidTestImplementation 'android.arch.persistence.room:testing:1.1.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - testImplementation 'androidx.test.ext:junit:1.1.1' - debugImplementation 'im.dino:dbinspector:3.4.1@aar' - implementation 'io.reactivex.rxjava2:rxjava:2.2.12' - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' - implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.4.0' - implementation 'com.uber.autodispose:autodispose:1.4.0' - implementation 'androidx.paging:paging-runtime-ktx:2.1.0' + androidTestImplementation "android.arch.persistence.room:testing:1.1.1" + androidTestImplementation "androidx.test.ext:junit:1.1.1" - //Glide - implementation 'com.github.bumptech.glide:glide:4.10.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:4.10.0' - - //Add some useful extensions - implementation 'androidx.core:core-ktx:1.2.0-alpha01' + debugImplementation "im.dino:dbinspector:4.0.0@aar" implementation 'net.accelf:easter:1.0.1' } diff --git a/app/src/blue/res/xml-v25/shortcuts.xml b/app/src/blue/res/xml-v25/shortcuts.xml deleted file mode 100644 index d0aaa2e80..000000000 --- a/app/src/blue/res/xml-v25/shortcuts.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/green/res/xml-v25/shortcuts.xml b/app/src/green/res/xml-v25/shortcuts.xml deleted file mode 100644 index 130274fae..000000000 --- a/app/src/green/res/xml-v25/shortcuts.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 53cf5bb6a..b76e03551 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,8 @@ + android:resource="@xml/share_shortcuts" /> + + android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize"> @@ -91,7 +92,8 @@ + android:value="androidx.sharetarget.ChooserTargetServiceCompat" /> + + android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" /> @@ -153,16 +155,8 @@ + - - - - - { diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 97f7932d4..8079717b4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -18,10 +18,10 @@ package com.keylesspalace.tusky import android.animation.ArgbEvaluator import android.content.Context import android.content.Intent +import android.content.res.ColorStateList import android.graphics.Color import android.graphics.PorterDuff import android.os.Bundle -import android.preference.PreferenceManager import android.view.Menu import android.view.MenuItem import android.view.View @@ -35,13 +35,18 @@ import androidx.core.content.ContextCompat import androidx.emoji.text.EmojiCompat import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.viewpager2.widget.MarginPageTransformer import com.bumptech.glide.Glide import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.shape.MaterialShapeDrawable +import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import com.keylesspalace.tusky.adapter.AccountFieldAdapter import com.keylesspalace.tusky.components.report.ReportActivity import com.keylesspalace.tusky.di.ViewModelFactory @@ -86,8 +91,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI @ColorInt private var toolbarColor: Int = 0 @ColorInt - private var backgroundColor: Int = 0 - @ColorInt private var statusBarColorTransparent: Int = 0 @ColorInt private var statusBarColorOpaque: Int = 0 @@ -107,6 +110,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + loadResources() makeNotificationBarTransparent() setContentView(R.layout.activity_account) @@ -119,7 +123,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false) hideFab = sharedPrefs.getBoolean("fabHide", false) - loadResources() setupToolbar() setupTabs() setupAccountViews() @@ -135,8 +138,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI * Load colors and dimensions from resources */ private fun loadResources() { - toolbarColor = ThemeUtils.getColor(this, R.attr.toolbar_background_color) - backgroundColor = ThemeUtils.getColor(this, android.R.attr.colorBackground) + toolbarColor = ThemeUtils.getColor(this, R.attr.colorSurface) statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter) statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark) avatarSize = resources.getDimension(R.dimen.account_activity_avatar_size) @@ -187,16 +189,21 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI */ private fun setupTabs() { // Setup the tabs and timeline pager. - adapter = AccountPagerAdapter(supportFragmentManager, viewModel.accountId) - val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media)) - adapter.setPageTitles(pageTitles) - accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin) - val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable, - R.drawable.tab_page_margin_dark) - accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable) + adapter = AccountPagerAdapter(this, viewModel.accountId) + accountFragmentViewPager.adapter = adapter accountFragmentViewPager.offscreenPageLimit = 2 - accountTabLayout.setupWithViewPager(accountFragmentViewPager) + + val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media)) + + TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { + tab, position -> + tab.text = pageTitles[position] + }.attach() + + val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin) + accountFragmentViewPager.setPageTransformer(MarginPageTransformer(pageMargin)) + accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabReselected(tab: TabLayout.Tab?) { tab?.position?.let { position -> @@ -211,9 +218,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI }) } - /** - * Setup toolbar - */ private fun setupToolbar() { // set toolbar top margin according to system window insets accountCoordinatorLayout.setOnApplyWindowInsetsListener { _, insets -> @@ -236,6 +240,23 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed) ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed) + val appBarElevation = resources.getDimension(R.dimen.actionbar_elevation) + + val toolbarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation) + toolbarBackground.fillColor = ColorStateList.valueOf(Color.TRANSPARENT) + accountToolbar.background = toolbarBackground + + accountHeaderInfoContainer.background = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation) + + val avatarBackground = MaterialShapeDrawable.createWithElevationOverlay(this, appBarElevation).apply { + fillColor = ColorStateList.valueOf(toolbarColor) + elevation = appBarElevation + shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius)) + .build() + } + accountAvatarImageView.background = avatarBackground + // Add a listener to change the toolbar icon color when it enters/exits its collapsed state. accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener { @@ -281,16 +302,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountAvatarImageView.visible(scaledAvatarSize > 0) - var transparencyPercent = abs(verticalOffset) / titleVisibleHeight.toFloat() - if (transparencyPercent > 1) transparencyPercent = 1f + val transparencyPercent = (abs(verticalOffset) / titleVisibleHeight.toFloat()).coerceAtMost(1f) window.statusBarColor = argbEvaluator.evaluate(transparencyPercent, statusBarColorTransparent, statusBarColorOpaque) as Int val evaluatedToolbarColor = argbEvaluator.evaluate(transparencyPercent, Color.TRANSPARENT, toolbarColor) as Int - val evaluatedTabBarColor = argbEvaluator.evaluate(transparencyPercent, backgroundColor, toolbarColor) as Int - accountToolbar.setBackgroundColor(evaluatedToolbarColor) - accountHeaderInfoContainer.setBackgroundColor(evaluatedTabBarColor) - accountTabLayout.setBackgroundColor(evaluatedTabBarColor) + + toolbarBackground.fillColor = ColorStateList.valueOf(evaluatedToolbarColor) + swipeToRefreshLayout.isEnabled = verticalOffset == 0 } }) @@ -300,7 +319,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun makeNotificationBarTransparent() { val decorView = window.decorView decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - window.statusBarColor = Color.TRANSPARENT + window.statusBarColor = statusBarColorTransparent } /** diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt index 6b2fd2744..9477b8440 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt @@ -17,13 +17,13 @@ package com.keylesspalace.tusky import android.os.Bundle -import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.appcompat.widget.SearchView import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter @@ -35,7 +35,6 @@ import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.State import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from -import com.uber.autodispose.autoDisposable import com.uber.autodispose.autoDispose import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.extensions.LayoutContainer diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 389a745e6..7cfe6fc61 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -23,7 +23,6 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; -import android.preference.PreferenceManager; import android.util.Log; import android.view.View; @@ -34,6 +33,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; import com.google.android.material.snackbar.Snackbar; import com.keylesspalace.tusky.adapter.AccountSelectionAdapter; @@ -52,8 +52,6 @@ import javax.inject.Inject; public abstract class BaseActivity extends AppCompatActivity implements Injectable { - @Inject - public ThemeUtils themeUtils; @Inject public AccountManager accountManager; @@ -75,8 +73,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab setTheme(R.style.TuskyBlackTheme); } - themeUtils.setAppNightMode(theme, this); - /* set the taskdescription programmatically, the theme would turn it blue */ String appName = getString(R.string.app_name); Bitmap appIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); diff --git a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt index f891c2366..416087a9e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt @@ -19,6 +19,7 @@ import android.content.Intent import android.os.Bundle import android.view.View import android.widget.LinearLayout +import android.widget.Toast import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -52,7 +53,7 @@ abstract class BottomSheetActivity : BaseActivity() { val bottomSheetLayout: LinearLayout = findViewById(R.id.item_status_bottom_sheet) bottomSheet = BottomSheetBehavior.from(bottomSheetLayout) bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN - bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + bottomSheet.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { if (newState == BottomSheetBehavior.STATE_HIDDEN) { cancelActiveSearch() @@ -64,7 +65,7 @@ abstract class BottomSheetActivity : BaseActivity() { } - open fun viewUrl(url: String, text: String) { + open fun viewUrl(url: String, text: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) { if (forceBrowser.contains(text)) { openLink(url) return @@ -95,11 +96,11 @@ abstract class BottomSheetActivity : BaseActivity() { return@subscribe } - openLink(url) + performUrlFallbackAction(url, lookupFallbackBehavior) }, { if (!getCancelSearchRequested(url)) { onEndSearch(url) - openLink(url) + performUrlFallbackAction(url, lookupFallbackBehavior) } }) @@ -120,6 +121,13 @@ abstract class BottomSheetActivity : BaseActivity() { startActivityWithSlideInAnimation(intent) } + protected open fun performUrlFallbackAction(url: String, fallbackBehavior: PostLookupFallbackBehavior) { + when (fallbackBehavior) { + PostLookupFallbackBehavior.OPEN_IN_BROWSER -> openLink(url) + PostLookupFallbackBehavior.DISPLAY_ERROR -> Toast.makeText(this, getString(R.string.post_lookup_error_format, url), Toast.LENGTH_SHORT).show() + } + } + @VisibleForTesting fun onBeginSearch(url: String) { searchUrl = url @@ -201,3 +209,8 @@ fun looksLikeMastodonUrl(urlString: String): Boolean { path.matches("^[^@]+@[^@]+$".toRegex()) } + +enum class PostLookupFallbackBehavior { + OPEN_IN_BROWSER, + DISPLAY_ERROR, +} diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index ea2aef639..7deb2dd7f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -26,9 +26,9 @@ import android.text.method.LinkMovementMethod import android.util.Log import android.view.MenuItem import android.view.View -import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AlertDialog +import com.bumptech.glide.Glide import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.AccessToken import com.keylesspalace.tusky.entity.AppCredentials @@ -48,9 +48,6 @@ class LoginActivity : BaseActivity(), Injectable { lateinit var mastodonApi: MastodonApi private lateinit var preferences: SharedPreferences - private var domain: String = "" - private var clientId: String? = null - private var clientSecret: String? = null private val oauthRedirectUri: String get() { @@ -64,10 +61,16 @@ class LoginActivity : BaseActivity(), Injectable { setContentView(R.layout.activity_login) - if (savedInstanceState != null) { - domain = savedInstanceState.getString(DOMAIN)!! - clientId = savedInstanceState.getString(CLIENT_ID) - clientSecret = savedInstanceState.getString(CLIENT_SECRET) + if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) { + domainEditText.setText(BuildConfig.CUSTOM_INSTANCE) + domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length) + } + + if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) { + Glide.with(loginLogo) + .load(BuildConfig.CUSTOM_LOGO_URL) + .placeholder(null) + .into(loginLogo) } preferences = getSharedPreferences( @@ -113,13 +116,6 @@ class LoginActivity : BaseActivity(), Injectable { return super.onOptionsItemSelected(item) } - override fun onSaveInstanceState(outState: Bundle) { - outState.putString(DOMAIN, domain) - outState.putString(CLIENT_ID, clientId) - outState.putString(CLIENT_SECRET, clientSecret) - super.onSaveInstanceState(outState) - } - /** * Obtain the oauth client credentials for this app. This is only necessary the first time the * app is run on a given server instance. So, after the first authentication, they are @@ -129,7 +125,7 @@ class LoginActivity : BaseActivity(), Injectable { loginButton.isEnabled = false - domain = canonicalizeDomain(domainEditText.text.toString()) + val domain = canonicalizeDomain(domainEditText.text.toString()) try { HttpUrl.Builder().host(domain).scheme("https").build() @@ -150,10 +146,16 @@ class LoginActivity : BaseActivity(), Injectable { return } val credentials = response.body() - clientId = credentials!!.clientId - clientSecret = credentials.clientSecret + val clientId = credentials!!.clientId + val clientSecret = credentials.clientSecret - redirectUserToAuthorizeAndLogin(domainEditText) + preferences.edit() + .putString("domain", domain) + .putString("clientId", clientId) + .putString("clientSecret", clientSecret) + .apply() + + redirectUserToAuthorizeAndLogin(domain, clientId) } override fun onFailure(call: Call, t: Throwable) { @@ -166,22 +168,22 @@ class LoginActivity : BaseActivity(), Injectable { mastodonApi .authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri, - OAUTH_SCOPES, getString(R.string.app_website)) + OAUTH_SCOPES, getString(R.string.tusky_website)) .enqueue(callback) setLoading(true) } - private fun redirectUserToAuthorizeAndLogin(editText: EditText) { + private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) { /* To authorize this app and log in it's necessary to redirect to the domain given, - * activity_login there, and the server will redirect back to the app with its response. */ + * login there, and the server will redirect back to the app with its response. */ val endpoint = MastodonApi.ENDPOINT_AUTHORIZE - val redirectUri = oauthRedirectUri - val parameters = HashMap() - parameters["client_id"] = clientId!! - parameters["redirect_uri"] = redirectUri - parameters["response_type"] = "code" - parameters["scope"] = OAUTH_SCOPES + val parameters = mapOf( + "client_id" to clientId, + "redirect_uri" to oauthRedirectUri, + "response_type" to "code", + "scope" to OAUTH_SCOPES + ) val url = "https://" + domain + endpoint + "?" + toQueryString(parameters) val uri = Uri.parse(url) if (!openInCustomTab(uri, this)) { @@ -189,21 +191,12 @@ class LoginActivity : BaseActivity(), Injectable { if (viewIntent.resolveActivity(packageManager) != null) { startActivity(viewIntent) } else { - editText.error = getString(R.string.error_no_web_browser_found) + domainEditText.error = getString(R.string.error_no_web_browser_found) setLoading(false) } } } - override fun onStop() { - super.onStop() - preferences.edit() - .putString("domain", domain) - .putString("clientId", clientId) - .putString("clientSecret", clientSecret) - .apply() - } - override fun onStart() { super.onStart() /* Check if we are resuming during authorization by seeing if the intent contains the @@ -216,14 +209,12 @@ class LoginActivity : BaseActivity(), Injectable { val code = uri.getQueryParameter("code") val error = uri.getQueryParameter("error") - /* During the redirect roundtrip this Activity usually dies, which wipes out the - * instance variables, so they have to be recovered from where they were saved in - * SharedPreferences. */ - domain = preferences.getNonNullString(DOMAIN, "") - clientId = preferences.getString(CLIENT_ID, null) - clientSecret = preferences.getString(CLIENT_SECRET, null) + /* restore variables from SharedPreferences */ + val domain = preferences.getNonNullString(DOMAIN, "") + val clientId = preferences.getNonNullString(CLIENT_ID, "") + val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "") - if (code != null && domain.isNotEmpty() && !clientId.isNullOrEmpty() && !clientSecret.isNullOrEmpty()) { + if (code != null && domain.isNotEmpty() && clientId.isNotEmpty() && clientSecret.isNotEmpty()) { setLoading(true) /* Since authorization has succeeded, the final step to log in is to exchange @@ -231,7 +222,7 @@ class LoginActivity : BaseActivity(), Injectable { val callback = object : Callback { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { - onLoginSuccess(response.body()!!.accessToken) + onLoginSuccess(response.body()!!.accessToken, domain) } else { setLoading(false) domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token) @@ -250,7 +241,7 @@ class LoginActivity : BaseActivity(), Injectable { } } - mastodonApi.fetchOAuthToken(domain, clientId!!, clientSecret!!, redirectUri, code, + mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code, "authorization_code").enqueue(callback) } else if (error != null) { /* Authorization failed. Put the error response where the user can read it and they @@ -286,7 +277,7 @@ class LoginActivity : BaseActivity(), Injectable { return intent.getBooleanExtra(LOGIN_MODE, false) } - private fun onLoginSuccess(accessToken: String) { + private fun onLoginSuccess(accessToken: String, domain: String) { setLoading(true) @@ -351,7 +342,7 @@ class LoginActivity : BaseActivity(), Injectable { .setToolbarColor(toolbarColor) .build() try { - customTabsIntent.launchUrl(context, uri) + customTabsIntent.launchUrl(context, uri) } catch (e: ActivityNotFoundException) { Log.w(TAG, "Activity was not found for intent $customTabsIntent") return false diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 13037abc9..6d6d25481 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -22,12 +22,10 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -35,14 +33,18 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.emoji.text.EmojiCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; -import androidx.viewpager.widget.ViewPager; +import androidx.preference.PreferenceManager; +import androidx.viewpager2.widget.MarginPageTransformer; +import androidx.viewpager2.widget.ViewPager2; import com.bumptech.glide.Glide; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import com.keylesspalace.tusky.appstore.CacheUpdater; import com.keylesspalace.tusky.appstore.DrawerFooterClickedEvent; import com.keylesspalace.tusky.appstore.EventHub; @@ -58,7 +60,7 @@ import com.keylesspalace.tusky.interfaces.ReselectableFragment; import com.keylesspalace.tusky.pager.MainPagerAdapter; import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.NotificationHelper; -import com.keylesspalace.tusky.util.ThemeUtils; +import com.keylesspalace.tusky.util.ShareShortcutHelper; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.AccountHeader; import com.mikepenz.materialdrawer.AccountHeaderBuilder; @@ -121,7 +123,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut private AccountHeader headerResult; private Drawer drawer; private TabLayout tabLayout; - private ViewPager viewPager; + private ViewPager2 viewPager; private SharedPreferences defPrefs; private int notificationTabPosition; @@ -149,7 +151,18 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut boolean showNotificationTab = false; if (intent != null) { + + /** there are two possibilities the accountId can be passed to MainActivity: + - from our code as long 'account_id' + - from share shortcuts as String 'android.intent.extra.shortcut.ID' + */ long accountId = intent.getLongExtra(NotificationHelper.ACCOUNT_ID, -1); + if(accountId == -1) { + String accountIdString = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID); + if(accountIdString != null) { + accountId = Long.parseLong(accountIdString); + } + } boolean accountRequested = (accountId != -1); if (accountRequested) { @@ -187,7 +200,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut setContentView(R.layout.activity_main); composeButton = findViewById(R.id.floating_btn); - ImageButton drawerToggle = findViewById(R.id.drawer_toggle); tabLayout = findViewById(R.id.tab_layout); viewPager = findViewById(R.id.pager); @@ -199,10 +211,6 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut setupDrawer(); - // Setup the navigation drawer toggle button. - ThemeUtils.setDrawableTint(this, drawerToggle.getDrawable(), R.attr.toolbar_icon_tint); - drawerToggle.setOnClickListener(v -> drawer.openDrawer()); - /* Fetch user info while we're doing other things. This has to be done after setting up the * drawer, though, because its callback touches the header in the drawer. */ fetchUserInfo(); @@ -212,10 +220,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut defPrefs = PreferenceManager.getDefaultSharedPreferences(this); int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin); - viewPager.setPageMargin(pageMargin); - Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable, - R.drawable.tab_page_margin_dark); - viewPager.setPageMarginDrawable(pageMarginDrawable); + viewPager.setPageTransformer(new MarginPageTransformer(pageMargin)); if (defPrefs.getBoolean("viewPagerOffScreenLimit", false)) { viewPager.setOffscreenPageLimit(9); } @@ -349,7 +354,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut if (intent != null) { String statusUrl = intent.getStringExtra(STATUS_URL); if (statusUrl != null) { - viewUrl(statusUrl, statusUrl); + viewUrl(statusUrl, statusUrl, PostLookupFallbackBehavior.DISPLAY_ERROR); } } } @@ -439,6 +444,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut .withHasStableIds(true) .withSelectedItem(-1) .withDrawerItems(listItems) + .withToolbar(findViewById(R.id.main_toolbar)) .withOnDrawerItemClickListener((view, position, drawerItem) -> { if (drawerItem != null) { long drawerItemIdentifier = drawerItem.getIdentifier(); @@ -534,10 +540,11 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut private void setupTabs(boolean selectNotificationTab) { List tabs = accountManager.getActiveAccount().getTabPreferences(); - adapter = new MainPagerAdapter(tabs, getSupportFragmentManager()); + adapter = new MainPagerAdapter(tabs, this); viewPager.setAdapter(adapter); - tabLayout.setupWithViewPager(viewPager); + new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { }).attach(); + tabLayout.removeAllTabs(); for (int i = 0; i < tabs.size(); i++) { TabLayout.Tab tab = tabLayout.newTab() @@ -610,6 +617,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut NotificationHelper.deleteNotificationChannelsForAccount(accountManager.getActiveAccount(), MainActivity.this); cacheUpdater.clearForUser(activeAccount.getId()); conversationRepository.deleteCacheForAccount(activeAccount.getId()); + ShareShortcutHelper.removeShortcut(this, activeAccount); AccountEntity newAccount = accountManager.logActiveAccountOut(); @@ -667,6 +675,8 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut updateProfiles(); + ShareShortcutHelper.updateShortcut(this, accountManager.getActiveAccount()); + } private void updateProfiles() { diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt index f017f3b4e..0ab7477c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt @@ -19,11 +19,10 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle -import android.preference.PreferenceManager import android.util.Log import android.view.MenuItem -import androidx.appcompat.app.AppCompatDelegate import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.fragment.preference.* @@ -123,18 +122,11 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference "appTheme" -> { val theme = sharedPreferences.getNonNullString("appTheme", ThemeUtils.APP_THEME_DEFAULT) Log.d("activeTheme", theme) - themeUtils.setAppNightMode(theme, this) + ThemeUtils.setAppNightMode(theme) restartActivitiesOnExit = true this.restartCurrentActivity() - // MODE_NIGHT_FOLLOW_SYSTEM workaround part 2 :/ - when(theme){ - ThemeUtils.THEME_SYSTEM -> { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) - } - } - //workaround end } "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "viewPagerOffScreenLimit" -> { restartActivitiesOnExit = true diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java index 39c21cdc1..915b835f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java @@ -17,10 +17,11 @@ package com.keylesspalace.tusky; import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; import android.content.res.Configuration; -import android.preference.PreferenceManager; import androidx.emoji.text.EmojiCompat; +import androidx.preference.PreferenceManager; import androidx.room.Room; import com.evernote.android.job.JobManager; @@ -30,6 +31,7 @@ import com.keylesspalace.tusky.di.AppInjector; import com.keylesspalace.tusky.util.EmojiCompatFont; import com.keylesspalace.tusky.util.LocaleManager; import com.keylesspalace.tusky.util.NotificationPullJobCreator; +import com.keylesspalace.tusky.util.ThemeUtils; import com.uber.autodispose.AutoDisposePlugins; import org.conscrypt.Conscrypt; @@ -91,6 +93,7 @@ public class TuskyApplication extends Application implements HasAndroidInjector initAppInjector(); initEmojiCompat(); + initNightMode(); JobManager.create(this).addJobCreator(notificationPullJobCreator); @@ -133,6 +136,12 @@ public class TuskyApplication extends Application implements HasAndroidInjector AppInjector.INSTANCE.init(this); } + protected void initNightMode() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + String theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT); + ThemeUtils.setAppNightMode(theme); + } + public ServiceLocator getServiceLocator() { return serviceLocator; } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index 2791ba2f1..66bf8e8df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -37,9 +37,10 @@ import android.view.View import android.webkit.MimeTypeMap import android.widget.Toast import androidx.core.content.FileProvider +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle -import androidx.viewpager.widget.PagerAdapter -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 import com.bumptech.glide.Glide import com.bumptech.glide.request.FutureTarget import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID @@ -110,23 +111,23 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener // but it cannot be expressed and if I don't specify type explicitly compilation fails // (probably a bug in compiler) - val adapter: PagerAdapter = if (attachments != null) { + val adapter: ViewMediaAdapter = if (attachments != null) { val realAttachs = attachments!!.map(AttachmentViewData::attachment) // Setup the view pager. - ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition) + ImagePagerAdapter(this, realAttachs, initialPosition) } else { val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL) ?: throw IllegalArgumentException("attachment list or avatar url has to be set") - AvatarImagePagerAdapter(supportFragmentManager, avatarUrl) + AvatarImagePagerAdapter(this, avatarUrl) } viewPager.adapter = adapter - viewPager.currentItem = initialPosition - viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + viewPager.setCurrentItem(initialPosition, false) + viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { - toolbar.title = adapter.getPageTitle(position) + toolbar.title = getPageTitle(position) } }) @@ -136,7 +137,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true) actionBar.setDisplayShowHomeEnabled(true) - actionBar.title = adapter.getPageTitle(initialPosition) + actionBar.title = getPageTitle(initialPosition) } toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } toolbar.setOnMenuItemClickListener { item: MenuItem -> @@ -153,7 +154,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener window.statusBarColor = Color.BLACK window.sharedElementEnterTransition.addListener(object : NoopTransitionListener { override fun onTransitionEnd(transition: Transition) { - (adapter as SharedElementTransitionListener).onTransitionEnd() + adapter.onTransitionEnd(viewPager.currentItem) window.sharedElementEnterTransition.removeListener(this) } }) @@ -198,6 +199,13 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener .start() } + private fun getPageTitle(position: Int): CharSequence { + if(attachments == null) { + return "" + } + return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size) + } + private fun downloadMedia() { val url = attachments!![viewPager.currentItem].attachment.url val filename = Uri.parse(url).lastPathSegment @@ -227,7 +235,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener private fun copyLink() { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url) + clipboard.setPrimaryClip(ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url)) } private fun shareMedia() { @@ -323,8 +331,8 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener } } -interface SharedElementTransitionListener { - fun onTransitionEnd() +abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) { + abstract fun onTransitionEnd(position: Int) } interface NoopTransitionListener : Transition.TransitionListener { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt index 74beda73a..f4892f9ba 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt @@ -16,16 +16,15 @@ package com.keylesspalace.tusky.adapter import android.content.Context -import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.util.CustomEmojiHelper import com.keylesspalace.tusky.util.loadAvatar - import kotlinx.android.synthetic.main.item_autocomplete_account.view.* class AccountSelectionAdapter(context: Context) : ArrayAdapter(context, R.layout.item_autocomplete_account) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java index 119669a99..edd93082b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java @@ -1,13 +1,13 @@ package com.keylesspalace.tusky.adapter; -import androidx.recyclerview.widget.RecyclerView; - import android.content.SharedPreferences; -import android.preference.PreferenceManager; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.RecyclerView; + import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.AccountActionListener; diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index cc861df24..70a44ba8c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -15,10 +15,6 @@ package com.keylesspalace.tusky.adapter; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -26,6 +22,10 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.RecyclerView; + import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.AccountActionListener; diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index a82a10ea4..37f09f019 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -15,10 +15,6 @@ package com.keylesspalace.tusky.adapter; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -26,6 +22,10 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.RecyclerView; + import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.AccountActionListener; diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index 5f494658b..a7d145454 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -1,9 +1,5 @@ package com.keylesspalace.tusky.adapter; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import android.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,6 +7,10 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.RecyclerView; + import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.interfaces.AccountActionListener; diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt index adfc681f8..0adc0c371 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -29,6 +29,7 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper import com.keylesspalace.tusky.util.HtmlUtils import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.viewdata.PollOptionViewData +import com.keylesspalace.tusky.viewdata.buildDescription import com.keylesspalace.tusky.viewdata.calculatePercent class PollAdapter: RecyclerView.Adapter() { @@ -71,10 +72,7 @@ class PollAdapter: RecyclerView.Adapter() { when(mode) { RESULT -> { val percent = calculatePercent(option.votesCount, voteCount) - - val pollOptionText = holder.resultTextView.context.getString(R.string.poll_option_format, percent, option.title) - - val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, holder.resultTextView) + val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(buildDescription(option.title, percent, holder.resultTextView.context), emojis, holder.resultTextView) holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) val level = percent * 100 diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 9a4bcbb11..fbaa49742 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -57,6 +57,8 @@ import at.connyduck.sparkbutton.SparkButton; import at.connyduck.sparkbutton.SparkEventListener; import kotlin.collections.CollectionsKt; +import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription; + public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { public static class Key { public static final String KEY_CREATED = "created"; @@ -390,7 +392,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } else { inactiveId = ThemeUtils.getDrawableId(reblogButton.getContext(), 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.setActiveImage(activeId); @@ -905,10 +907,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { for (int i = 0; i < args.length; i++) { if (i < options.size()) { int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotesCount()); - args[i] = HtmlUtils.fromHtml(context.getString( - R.string.poll_option_format, - percent, - options.get(i).getTitle())); + args[i] = buildDescription(options.get(i).getTitle(), percent, context); } else { args[i] = ""; } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index 4757b5676..6c1b8c6f6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -16,7 +16,6 @@ package com.keylesspalace.tusky.components.conversation; import android.content.Context; -import android.preference.PreferenceManager; import android.text.InputFilter; import android.text.TextUtils; import android.view.View; @@ -24,6 +23,7 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.ToggleButton; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; import com.keylesspalace.tusky.R; diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index 6c73b0a79..4ac985a85 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -17,13 +17,13 @@ package com.keylesspalace.tusky.components.conversation import android.content.Intent import android.os.Bundle -import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.paging.PagedList +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.SimpleItemAnimator diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt index 515d34f0e..dd28b0cc8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt @@ -59,6 +59,7 @@ class ConversationsViewModel @Inject constructor( } .subscribeOn(Schedulers.io()) .doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) } + .onErrorReturnItem(0) .subscribe() .addTo(disposables) } @@ -77,6 +78,7 @@ class ConversationsViewModel @Inject constructor( } .subscribeOn(Schedulers.io()) .doOnError { t -> Log.w("ConversationViewModel", "Failed to favourite conversation", t) } + .onErrorReturnItem(0) .subscribe() .addTo(disposables) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 6d4cc9e31..46f84ec6f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -16,7 +16,6 @@ package com.keylesspalace.tusky.components.report.fragments import android.os.Bundle -import android.preference.PreferenceManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -26,6 +25,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.paging.PagedList +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.SimpleItemAnimator diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index 5b2a23dbc..394c1336e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -23,6 +23,7 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.widget.SearchView import androidx.lifecycle.ViewModelProviders +import com.google.android.material.tabs.TabLayoutMediator import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter @@ -57,9 +58,13 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha } private fun setupPages() { - pages.adapter = SearchPagerAdapter(this, supportFragmentManager) - tabs.setupWithViewPager(pages) + pages.adapter = SearchPagerAdapter(this) pages.offscreenPageLimit = 4 + + TabLayoutMediator(tabs, pages) { + tab, position -> + tab.text = getPageTitle(position) + }.attach() } override fun onNewIntent(intent: Intent) { @@ -75,9 +80,7 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha .actionView as SearchView setupSearchView(searchView) - if (viewModel.currentQuery != null) { - searchView.setQuery(viewModel.currentQuery, false) - } + searchView.setQuery(viewModel.currentQuery, false) return true } @@ -100,9 +103,19 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha 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) { if (Intent.ACTION_SEARCH == intent.action) { - viewModel.currentQuery = intent.getStringExtra(SearchManager.QUERY) + viewModel.currentQuery = intent.getStringExtra(SearchManager.QUERY) ?: "" viewModel.search(viewModel.currentQuery) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt index 61f618ac0..11b5bccd1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt @@ -21,6 +21,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.addTo import javax.inject.Inject class SearchViewModel @Inject constructor( @@ -29,7 +30,7 @@ class SearchViewModel @Inject constructor( private val timelineCases: TimelineCases, private val accountManager: AccountManager) : ViewModel() { - var currentQuery: String? = null + var currentQuery: String = "" var activeAccount: AccountEntity? get() = accountManager.activeAccount @@ -72,7 +73,7 @@ class SearchViewModel @Inject constructor( private val loadedStatuses = ArrayList>() private val loadedNotestockStatuses = ArrayList>() - fun search(query: String?) { + fun search(query: String) { loadedStatuses.clear() repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) { (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) { it?.accounts ?: emptyList() } - val hashtagQuery = if (query != null && query.startsWith("#")) query else "#$query" + val hashtagQuery = if (query.startsWith("#")) query else "#$query" repoResultHashTag.value = hashtagsRepository.getSearchData(SearchType.Hashtag, hashtagQuery, disposables) { it?.hashtags ?: emptyList() @@ -107,9 +108,14 @@ class SearchViewModel @Inject constructor( fun removeItem(status: Pair) { timelineCases.delete(status.first.id) - .subscribe() - if (loadedStatuses.remove(status)) - repoResultStatus.value?.refresh?.invoke() + .subscribe({ + if (loadedStatuses.remove(status)) + repoResultStatus.value?.refresh?.invoke() + }, { + err -> Log.d(TAG, "Failed to delete status", err) + }) + .addTo(disposables) + } fun removeNotestockItem(status: Pair) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index 8c5c84c65..918e7a427 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -22,12 +22,8 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.AccountViewHolder -import com.keylesspalace.tusky.adapter.StatusViewHolder import com.keylesspalace.tusky.entity.Account -import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.interfaces.LinkListener -import com.keylesspalace.tusky.interfaces.StatusActionListener -import com.keylesspalace.tusky.viewdata.StatusViewData class SearchAccountsAdapter(private val linkListener: LinkListener) : PagedListAdapter(STATUS_COMPARATOR) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt index 94372ee57..d1518dd2d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt @@ -15,18 +15,17 @@ package com.keylesspalace.tusky.components.search.adapter -import android.content.Context import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import com.keylesspalace.tusky.R +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment import com.keylesspalace.tusky.components.search.fragments.SearchNotestockFragment import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment -class SearchPagerAdapter(private val context: Context, manager: FragmentManager) : FragmentPagerAdapter(manager) { - override fun getItem(position: Int): Fragment { +class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { + + override fun createFragment(position: Int): Fragment { return when (position) { 0 -> SearchStatusesFragment.newInstance() 1 -> SearchAccountsFragment.newInstance() @@ -36,15 +35,6 @@ class SearchPagerAdapter(private val context: Context, manager: FragmentManager) } } - override fun getPageTitle(position: Int): CharSequence? { - 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 getItemCount() = 4 - override fun getCount(): Int = 4 } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt index 1d677794f..35c5bb658 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt @@ -28,7 +28,7 @@ import javax.inject.Inject abstract class SearchFragment : Fragment(), LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener { - private var isSwipeToRefreshEnabled: Boolean = true + private var snackbarErrorRetry: Snackbar? = null @Inject lateinit var viewModelFactory: ViewModelFactory diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index 230114abb..fed4c3b54 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -24,7 +24,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Environment -import android.preference.PreferenceManager import android.util.Log import android.view.View import android.widget.Toast @@ -36,6 +35,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LiveData import androidx.paging.PagedList import androidx.paging.PagedListAdapter +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.keylesspalace.tusky.* @@ -305,8 +305,7 @@ open class SearchStatusesFragment : SearchFragment { val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText(null, statusUrl) - clipboard.primaryClip = clip + clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl)) return@setOnMenuItemClickListener true } R.id.status_open_as -> { diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index 1cbe2190f..bbc870614 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -19,8 +19,8 @@ package com.keylesspalace.tusky.di import android.app.Application import android.content.Context import android.content.SharedPreferences -import android.preference.PreferenceManager import androidx.localbroadcastmanager.content.LocalBroadcastManager +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.TuskyApplication import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.EventHubImpl diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt index 7c7c8342b..1b661bcb2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt @@ -62,8 +62,7 @@ data class Account( if (other !is Account) { return false } - val account = other as Account? - return account?.id == this.id + return other.id == this.id } fun deepEquals(other: Account): Boolean { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 3415b3400..06073d86d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -81,7 +81,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { private var currentCall: Call>? = null private val statuses = mutableListOf() private var fetchingStatus = FetchingStatus.NOT_FETCHING - private var isVisibleToUser: Boolean = false private lateinit var accountId: String @@ -216,7 +215,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } }) - if (isVisibleToUser) doInitialLoadingIfNeeded() + doInitialLoadingIfNeeded() } private fun refresh() { @@ -235,14 +234,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { 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() { if (isAdded) { statusView.hide() diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index ba4a1550a..0d0336307 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseBooleanArray; import android.view.LayoutInflater; @@ -39,6 +38,7 @@ import androidx.arch.core.util.Function; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.util.Pair; import androidx.lifecycle.Lifecycle; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.AsyncDifferConfig; import androidx.recyclerview.widget.AsyncListDiffer; import androidx.recyclerview.widget.DiffUtil; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index 6244f1687..3062869d7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -45,6 +45,7 @@ import com.keylesspalace.tusky.BottomSheetActivity; import com.keylesspalace.tusky.ComposeActivity; import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.PostLookupFallbackBehavior; import com.keylesspalace.tusky.ViewMediaActivity; import com.keylesspalace.tusky.ViewTagActivity; 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) { - bottomSheetActivity.viewUrl(url, text); + bottomSheetActivity.viewUrl(url, text, PostLookupFallbackBehavior.OPEN_IN_BROWSER); } protected void reply(Status status) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 036935976..619b76834 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -22,13 +22,29 @@ import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; -import android.preference.PreferenceManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; 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.keylesspalace.tusky.AccountListActivity; import com.keylesspalace.tusky.BaseActivity; @@ -86,22 +102,6 @@ import java.util.concurrent.TimeUnit; 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 io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -377,7 +377,7 @@ public class TimelineFragment extends SFragment implements Iterator> iterator = this.statuses.iterator(); while (iterator.hasNext()) { Either item = iterator.next(); - if(item.isRight()) { + if (item.isRight()) { Status status = item.asRight(); if (status.getId().length() < topId.length() || status.getId().compareTo(topId) < 0) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index 540469fac..5a50fd18c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -18,13 +18,12 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.text.TextUtils import android.widget.TextView -import com.keylesspalace.tusky.SharedElementTransitionListener import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.util.visible -abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListener { +abstract class ViewMediaFragment : BaseFragment() { private var toolbarVisibiltyDisposable: Function0? = null 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?) { val mediaActivity = activity as ViewMediaActivity setupMediaView(url, previewUrl) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 2f2b948b2..65d871974 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -31,6 +30,7 @@ import androidx.annotation.Nullable; import androidx.arch.core.util.Function; import androidx.core.util.Pair; import androidx.lifecycle.Lifecycle; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt index 0ab9e1369..dfc44fa61 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt @@ -92,9 +92,9 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), publicFiltersPreference = requirePreference("publicFilters") 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) - 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) notificationPreference.onPreferenceClickListener = this @@ -289,7 +289,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), private fun getTintedIcon(iconId: Int): Drawable? { 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 } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt index ff3d3a69e..95b21d3a3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt @@ -39,13 +39,13 @@ class PreferencesFragment : PreferenceFragmentCompat() { addPreferencesFromResource(R.xml.preferences) 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") - 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") - 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") timelineFilterPreferences.setOnPreferenceClickListener { @@ -68,11 +68,11 @@ class PreferencesFragment : PreferenceFragmentCompat() { } 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 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 updateStackTracePreference() diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java deleted file mode 100644 index de2f05234..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java +++ /dev/null @@ -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 . */ - -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 fragments = new SparseArray<>(TAB_COUNT); - - private final Set 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. */ + +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?>(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 + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt index 1beab552e..a3dfcf2ab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt @@ -1,13 +1,16 @@ package com.keylesspalace.tusky.pager import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import com.keylesspalace.tusky.SharedElementTransitionListener +import androidx.fragment.app.FragmentActivity +import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.fragment.ViewMediaFragment -class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avatarUrl: String) : FragmentPagerAdapter(fragmentManager), SharedElementTransitionListener { - override fun getItem(position: Int): Fragment { +class AvatarImagePagerAdapter( + activity: FragmentActivity, + private val avatarUrl: String +) : ViewMediaAdapter(activity) { + + override fun createFragment(position: Int): Fragment { return if (position == 0) { ViewMediaFragment.newAvatarInstance(avatarUrl) } 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) { } } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt index 983770d2a..4f813d8b1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt @@ -1,53 +1,42 @@ package com.keylesspalace.tusky.pager -import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter -import com.keylesspalace.tusky.SharedElementTransitionListener +import androidx.fragment.app.FragmentActivity +import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.fragment.ViewMediaFragment -import java.util.* +import java.lang.ref.WeakReference class ImagePagerAdapter( - fragmentManager: FragmentManager, + activity: FragmentActivity, private val attachments: List, private val initialPosition: Int -) : FragmentStatePagerAdapter(fragmentManager), SharedElementTransitionListener { +) : ViewMediaAdapter(activity) { - private var primaryItem: ViewMediaFragment? = null private var didTransition = false + private val fragments = MutableList?>(attachments.size) { null } - override fun setPrimaryItem(container: ViewGroup, position: Int, item: Any) { - super.setPrimaryItem(container, position, item) - this.primaryItem = item as ViewMediaFragment - } + override fun getItemCount() = attachments.size - override fun getItem(position: Int): Fragment { - return if (position >= 0 && position < attachments.size) { + override fun createFragment(position: Int): Fragment { + if (position >= 0 && position < attachments.size) { // 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 - // 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. - ViewMediaFragment.newInstance( + val fragment = ViewMediaFragment.newInstance( attachment = attachments[position], shouldStartPostponedTransition = !didTransition && position == initialPosition ) + fragments[position] = WeakReference(fragment) + return fragment } else { throw IllegalStateException() } } - override fun getCount(): Int { - return attachments.size - } - - override fun getPageTitle(position: Int): CharSequence { - return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size) - } - - override fun onTransitionEnd() { + override fun onTransitionEnd(position: Int) { this.didTransition = true - primaryItem?.onTransitionEnd() + fragments[position]?.get()?.onTransitionEnd() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt index 7f0558c35..58728340e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt @@ -15,49 +15,23 @@ package com.keylesspalace.tusky.pager -import android.util.SparseArray -import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter -import androidx.viewpager.widget.PagerAdapter +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter import com.keylesspalace.tusky.TabData +import java.lang.ref.WeakReference -class MainPagerAdapter(val tabs: List, manager: FragmentManager) : FragmentPagerAdapter(manager) { - private val fragments = SparseArray(tabs.size) +class MainPagerAdapter(val tabs: List, activity: FragmentActivity) : FragmentStateAdapter(activity) { + private val fragments = MutableList?>(tabs.size) { null } - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val tab = tabs[position] - return tab.fragment(tab.arguments) - } - - 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) + val fragment = tab.fragment(tab.arguments) + fragments[position] = WeakReference(fragment) return fragment } - override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { - super.destroyItem(container, position, `object`) - fragments.remove(position) - } + override fun getItemCount() = tabs.size - fun getFragment(position: Int): Fragment? = fragments[position] + fun getFragment(position: Int): Fragment? = fragments[position]?.get() } diff --git a/app/src/main/java/com/keylesspalace/tusky/service/AccountChooserService.kt b/app/src/main/java/com/keylesspalace/tusky/service/AccountChooserService.kt deleted file mode 100644 index b97ca3ee3..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/service/AccountChooserService.kt +++ /dev/null @@ -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 . */ - -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 { - val targets = mutableListOf() - 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 - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt similarity index 50% rename from app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java rename to app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt index 6aad5d228..f064089da 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java +++ b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.kt @@ -1,4 +1,4 @@ -/* Copyright 2017 Andrew Dawson +/* Copyright 2019 Tusky Contributors * * 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, * see . */ -package com.keylesspalace.tusky.service; +package com.keylesspalace.tusky.service -import android.annotation.TargetApi; -import android.content.Intent; -import android.service.quicksettings.TileService; +import android.annotation.TargetApi +import android.content.Intent +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 - * Created by ztepps on 4/3/17. + * Small Addition that adds in a QuickSettings tile + * opens the Compose activity or shows an account selector when multiple accounts are present */ @TargetApi(24) -public class TuskyTileService extends TileService { - public TuskyTileService() { - super(); - } +class TuskyTileService : TileService() { - @Override - public void onClick() { - Intent intent = new Intent(this, ComposeActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityAndCollapse(intent); + override fun onClick() { + val intent = Intent(this, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + action = Intent.ACTION_SEND + type = "text/plain" + } + startActivityAndCollapse(intent) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt index 77a765a1e..1ab3e3747 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt @@ -5,12 +5,12 @@ package com.keylesspalace.tusky.util import android.widget.ImageView import androidx.annotation.Px 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.keylesspalace.tusky.R -private val fitCenterTransformation = FitCenter() +private val centerCropTransformation = CenterCrop() 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) .load(url) .transform( - fitCenterTransformation, + centerCropTransformation, RoundedCorners(radius) ) .placeholder(R.drawable.avatar_default) @@ -34,7 +34,7 @@ fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boo .asBitmap() .load(url) .transform( - fitCenterTransformation, + centerCropTransformation, RoundedCorners(radius) ) .placeholder(R.drawable.avatar_default) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java index a040ff9e8..f6dd82f0a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java @@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.preference.PreferenceManager; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.method.LinkMovementMethod; @@ -32,6 +31,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.browser.customtabs.CustomTabsIntent; +import androidx.preference.PreferenceManager; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Status; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt b/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt index 3cb34e856..4a80bca20 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt @@ -18,9 +18,8 @@ package com.keylesspalace.tusky.util import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration -import android.preference.PreferenceManager - -import java.util.Locale +import androidx.preference.PreferenceManager +import java.util.* class LocaleManager(context: Context) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java index 805079a7a..4e0d298d7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java @@ -69,6 +69,8 @@ import java.util.concurrent.ExecutionException; import io.reactivex.Single; import io.reactivex.schedulers.Schedulers; +import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription; + public class NotificationHelper { 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)) .setGroup(account.getAccountId()) .setAutoCancel(true) + .setShortcutId(Long.toString(account.getId())) .setDefaults(0); // So it doesn't ring twice, notify only in Target callback setupPreferences(account, builder); @@ -627,9 +630,9 @@ public class NotificationHelper { builder.append('\n'); Poll poll = notification.getStatus().getPoll(); for(PollOption option: poll.getOptions()) { - int percent = PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount()); - CharSequence optionText = HtmlUtils.fromHtml(context.getString(R.string.poll_option_format, percent, option.getTitle())); - builder.append(optionText); + builder.append(buildDescription(option.getTitle(), + PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount()), + context)); builder.append('\n'); } return builder.toString(); diff --git a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java index 7647c2c84..22e13e5ec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java @@ -18,7 +18,9 @@ package com.keylesspalace.tusky.util; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; -import android.preference.PreferenceManager; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; import com.keylesspalace.tusky.BuildConfig; @@ -26,7 +28,6 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.util.concurrent.TimeUnit; -import androidx.annotation.NonNull; import okhttp3.Cache; import okhttp3.Interceptor; import okhttp3.OkHttpClient; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt new file mode 100644 index 000000000..5aef288cf --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt @@ -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 . */ + +@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())) + +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index fc0b7c9d2..85e89802a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -30,6 +30,7 @@ import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.view.MediaPreviewImageView import com.keylesspalace.tusky.viewdata.PollViewData +import com.keylesspalace.tusky.viewdata.buildDescription import com.keylesspalace.tusky.viewdata.calculatePercent import java.text.NumberFormat import java.text.SimpleDateFormat @@ -283,8 +284,8 @@ class StatusViewHelper(private val itemView: View) { if (i < options.size) { val percent = calculatePercent(options[i].votesCount, poll.votesCount) - val pollOptionText = pollResults[i].context.getString(R.string.poll_option_format, percent, options[i].title) - pollResults[i].text = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, pollResults[i]) + val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context) + pollResults[i].text = CustomEmojiHelper.emojifyText(pollOptionText, emojis, pollResults[i]) pollResults[i].visibility = View.VISIBLE val level = percent * 100 diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java index 23034701b..53b762921 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java @@ -29,27 +29,21 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatDelegate; - -import javax.inject.Inject; -import javax.inject.Singleton; +import android.util.TypedValue; /** * Provides runtime compatibility to obtain theme information and re-theme views, especially where * the ability to do so is not supported in resource files. */ -@Singleton public class ThemeUtils { - @Inject - public ThemeUtils(){} - public static final String APP_THEME_DEFAULT = ThemeUtils.THEME_NIGHT; private static final String THEME_NIGHT = "night"; public static final String THEME_DAY = "day"; private static final String THEME_BLACK = "black"; 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, @DrawableRes int fallbackDrawable) { @@ -63,8 +57,9 @@ public class ThemeUtils { return context.getDrawable(resourceId); } - public static @DrawableRes int getDrawableId(@NonNull Context context, @AttrRes int attribute, - @DrawableRes int fallbackDrawableId) { + @DrawableRes + public static int getDrawableId(@NonNull Context context, @AttrRes int attribute, + @DrawableRes int fallbackDrawableId) { TypedValue value = new TypedValue(); if (context.getTheme().resolveAttribute(attribute, value, true)) { 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(); if (context.getTheme().resolveAttribute(attribute, value, true)) { 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(); context.getTheme().resolveAttribute(attribute, value, true); return value.resourceId; } /** 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); if(drawable == null) { return null; @@ -102,30 +100,21 @@ public class ThemeUtils { drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN); } - public void setAppNightMode(String flavor, Context context) { + public static void setAppNightMode(String flavor) { switch (flavor) { default: case THEME_NIGHT: + case THEME_BLACK: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); break; case THEME_DAY: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); break; - case THEME_BLACK: - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - break; case THEME_AUTO: - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO); + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_TIME); break; case THEME_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; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ComposeScheduleView.java b/app/src/main/java/com/keylesspalace/tusky/view/ComposeScheduleView.java index 8efe68639..dc58f86ec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/ComposeScheduleView.java +++ b/app/src/main/java/com/keylesspalace/tusky/view/ComposeScheduleView.java @@ -120,7 +120,8 @@ public class ComposeScheduleView extends ConstraintLayout { private void openPickDateDialog() { long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000; CalendarConstraints calendarConstraints = new CalendarConstraints.Builder() - .setValidator(new DateValidatorPointForward(yesterday)) + .setValidator( + DateValidatorPointForward.from(yesterday)) .build(); if (scheduleDateTime == null) { scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java b/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java deleted file mode 100644 index 22e967afb..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/view/ImageViewPager.java +++ /dev/null @@ -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 . */ - -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; - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt index 3b893f8b0..dff0c9f60 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt @@ -15,8 +15,13 @@ 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.PollOption +import com.keylesspalace.tusky.util.HtmlUtils import java.util.* 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? { if (this == null) return null return PollViewData( diff --git a/app/src/main/res/color/tab_icon_color.xml b/app/src/main/res/color/tab_icon_color.xml deleted file mode 100644 index 8edd8d373..000000000 --- a/app/src/main/res/color/tab_icon_color.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-night/avatar_background.xml b/app/src/main/res/drawable-night/avatar_background.xml deleted file mode 100644 index 4fb7dbda1..000000000 --- a/app/src/main/res/drawable-night/avatar_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar_background.xml b/app/src/main/res/drawable/avatar_background.xml deleted file mode 100644 index 848919782..000000000 --- a/app/src/main/res/drawable/avatar_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_24dp.xml b/app/src/main/res/drawable/ic_menu_24dp.xml deleted file mode 100644 index 8ec2ddf73..000000000 --- a/app/src/main/res/drawable/ic_menu_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reblog_active_24dp.xml b/app/src/main/res/drawable/ic_reblog_active_24dp.xml new file mode 100644 index 000000000..8d28a4005 --- /dev/null +++ b/app/src/main/res/drawable/ic_reblog_active_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_reblog_dark_18dp.xml b/app/src/main/res/drawable/ic_reblog_dark_24dp.xml similarity index 100% rename from app/src/main/res/drawable/ic_reblog_dark_18dp.xml rename to app/src/main/res/drawable/ic_reblog_dark_24dp.xml diff --git a/app/src/main/res/drawable/ic_reblog_light_18dp.xml b/app/src/main/res/drawable/ic_reblog_light_24dp.xml similarity index 100% rename from app/src/main/res/drawable/ic_reblog_light_18dp.xml rename to app/src/main/res/drawable/ic_reblog_light_24dp.xml diff --git a/app/src/main/res/drawable/reblog_active.xml b/app/src/main/res/drawable/reblog_active.xml deleted file mode 100644 index d27942e55..000000000 --- a/app/src/main/res/drawable/reblog_active.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/tab_page_margin_black.xml b/app/src/main/res/drawable/tab_page_margin_black.xml deleted file mode 100644 index a9d6d6cfa..000000000 --- a/app/src/main/res/drawable/tab_page_margin_black.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_page_margin_dark.xml b/app/src/main/res/drawable/tab_page_margin_dark.xml deleted file mode 100644 index 3bd2e0c2a..000000000 --- a/app/src/main/res/drawable/tab_page_margin_dark.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_page_margin_light.xml b/app/src/main/res/drawable/tab_page_margin_light.xml deleted file mode 100644 index bc46cd5f9..000000000 --- a/app/src/main/res/drawable/tab_page_margin_light.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline.xml b/app/src/main/res/layout-sw640dp/fragment_timeline.xml index 27e983e5c..56150457d 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:layout_height="match_parent"> + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml b/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml index e0d0583a5..82a13db74 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline_notifications.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:layout_height="match_parent"> \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml index 12a0f476e..b8e1cf9a9 100644 --- a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml +++ b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml @@ -1,20 +1,19 @@ + android:layout_height="match_parent"> + android:layout_gravity="center_horizontal"> diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 51c889acf..254029e76 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -16,8 +16,8 @@ + android:layout_gravity="center" + android:textDirection="anyRtl"> + + + - @@ -317,8 +315,10 @@ android:layout_height="?attr/actionBarSize" android:layout_gravity="top" android:background="@android:color/transparent" + app:contentInsetStartWithNavigation="0dp" app:layout_collapseMode="pin" app:layout_scrollFlags="scroll|enterAlways" /> + - + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 06a4bf426..70c912428 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -27,6 +27,7 @@ android:layout_height="178dp" android:layout_marginBottom="50dp" android:contentDescription="@null" + android:id="@+id/loginLogo" app:srcCompat="@drawable/elephant_friend" /> + android:layout_weight="1"> - + android:layout_height="wrap_content" + android:elevation="@dimen/actionbar_elevation"> - - - - - + app:contentInsetStartWithNavigation="0dp"> - + + + + + + + - diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index 992c2bd10..566e995c6 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -10,29 +10,29 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="@dimen/actionbar_elevation" - android:stateListAnimator="@null" app:layout_collapseMode="pin"> + + - - diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 4381e5993..a4ea14020 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -4,20 +4,20 @@ android:id="@+id/layoutRoot" android:layout_width="@dimen/timeline_width" android:layout_height="match_parent" - android:background="?attr/tab_page_margin_drawable"> + android:background="?attr/tab_page_margin_color"> + android:id="@+id/swipeRefreshLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/searchRecyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:background="?attr/window_background" + tools:listitem="@layout/item_account" /> diff --git a/app/src/main/res/layout/fragment_timeline.xml b/app/src/main/res/layout/fragment_timeline.xml index e246ed6fd..94431fffe 100644 --- a/app/src/main/res/layout/fragment_timeline.xml +++ b/app/src/main/res/layout/fragment_timeline.xml @@ -3,7 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="?attr/window_background"> + + tools:visibility="visible" /> + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timeline_notifications.xml b/app/src/main/res/layout/fragment_timeline_notifications.xml index 6f7dfe1d2..d27c6183a 100644 --- a/app/src/main/res/layout/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout/fragment_timeline_notifications.xml @@ -58,7 +58,8 @@ + android:layout_height="match_parent" + android:background="?attr/window_background" /> diff --git a/app/src/main/res/layout/fragment_view_image.xml b/app/src/main/res/layout/fragment_view_image.xml index 39bbd9fb6..faf1f806f 100644 --- a/app/src/main/res/layout/fragment_view_image.xml +++ b/app/src/main/res/layout/fragment_view_image.xml @@ -1,7 +1,7 @@ + android:layout_gravity="center" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_thread.xml b/app/src/main/res/layout/fragment_view_thread.xml index aa6877d41..a990942e3 100644 --- a/app/src/main/res/layout/fragment_view_thread.xml +++ b/app/src/main/res/layout/fragment_view_thread.xml @@ -9,5 +9,7 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?attr/window_background" android:scrollbars="vertical" /> + diff --git a/app/src/main/res/layout/fragment_view_video.xml b/app/src/main/res/layout/fragment_view_video.xml index 0247a9cb3..083bdb20f 100644 --- a/app/src/main/res/layout/fragment_view_video.xml +++ b/app/src/main/res/layout/fragment_view_video.xml @@ -2,9 +2,9 @@ @@ -14,6 +14,7 @@ android:layout_height="wrap_content" android:layout_marginTop="?attr/actionBarSize" android:background="#60000000" + android:hyphenationFrequency="full" android:lineSpacingMultiplier="1.1" android:padding="8dp" android:textAlignment="center" diff --git a/app/src/main/res/layout/item_follow.xml b/app/src/main/res/layout/item_follow.xml index 1af4f06c9..bed3795e0 100644 --- a/app/src/main/res/layout/item_follow.xml +++ b/app/src/main/res/layout/item_follow.xml @@ -36,8 +36,9 @@ android:contentDescription="@string/action_view_profile" android:scaleType="centerCrop" android:textSize="?attr/status_text_medium" + app:layout_constraintBottom_toBottomOf="@id/notification_username" 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" /> diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index f0b4f9511..ce5beab2b 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -106,6 +106,7 @@ android:id="@+id/status_content_warning_description" android:layout_width="0dp" android:layout_height="wrap_content" + android:hyphenationFrequency="full" android:importantForAccessibility="no" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorPrimary" @@ -146,6 +147,7 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:focusable="true" + android:hyphenationFrequency="full" android:importantForAccessibility="no" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorPrimary" @@ -448,7 +450,7 @@ app:layout_constraintEnd_toStartOf="@id/status_favourite" app:layout_constraintStart_toEndOf="@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:inactiveImage="?attr/status_reblog_inactive_drawable" sparkbutton:primaryColor="@color/tusky_blue" diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml index c83bd3bad..e2a8e8d41 100644 --- a/app/src/main/res/layout/item_status_detailed.xml +++ b/app/src/main/res/layout/item_status_detailed.xml @@ -84,6 +84,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" + android:hyphenationFrequency="full" android:importantForAccessibility="no" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorPrimary" @@ -120,6 +121,7 @@ android:layout_marginTop="4dp" android:layout_marginBottom="4dp" android:focusable="true" + android:hyphenationFrequency="full" android:importantForAccessibility="no" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorPrimary" @@ -536,7 +538,7 @@ app:layout_constraintEnd_toStartOf="@id/status_favourite" app:layout_constraintStart_toEndOf="@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:inactiveImage="?attr/status_reblog_inactive_drawable" sparkbutton:primaryColor="@color/tusky_blue" diff --git a/app/src/main/res/layout/item_status_notification.xml b/app/src/main/res/layout/item_status_notification.xml index 08697054d..27c93ad65 100644 --- a/app/src/main/res/layout/item_status_notification.xml +++ b/app/src/main/res/layout/item_status_notification.xml @@ -13,8 +13,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" - android:layout_marginBottom="6dp" android:layout_marginTop="8dp" + android:layout_marginBottom="6dp" android:drawablePadding="10dp" android:ellipsize="end" android:gravity="center_vertical" @@ -39,8 +39,8 @@ android:layout_alignParentStart="true" android:ellipsize="end" android:maxLines="1" - android:paddingEnd="@dimen/status_display_name_padding_end" android:paddingStart="0dp" + android:paddingEnd="@dimen/status_display_name_padding_end" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small" android:textColor="?android:textColorTertiary" android:textSize="?attr/status_text_medium" @@ -51,8 +51,8 @@ android:id="@+id/status_username" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_toEndOf="@id/status_display_name" android:layout_toStartOf="@+id/status_timestamp_info" + android:layout_toEndOf="@id/status_display_name" android:ellipsize="end" android:maxLines="1" android:textColor="?android:textColorTertiary" @@ -77,27 +77,27 @@ android:layout_height="wrap_content" android:layout_below="@id/status_name_bar" android:layout_toEndOf="@id/notification_status_avatar" + android:hyphenationFrequency="full" android:lineSpacingMultiplier="1.1" android:textColor="?android:textColorTertiary" android:textSize="?attr/status_text_medium" tools:text="Example CW text" /> - + android:id="@+id/button_toggle_notification_content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/notification_content" + android:layout_marginTop="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:textOff="@string/status_content_show_less" + android:textOn="@string/status_content_show_more" + android:textSize="?attr/status_text_medium" + android:visibility="gone" /> @@ -166,7 +167,7 @@ android:id="@+id/notification_notification_avatar" android:layout_width="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" /> diff --git a/app/src/main/res/layout/toolbar_basic.xml b/app/src/main/res/layout/toolbar_basic.xml index bda3db47b..71039105f 100644 --- a/app/src/main/res/layout/toolbar_basic.xml +++ b/app/src/main/res/layout/toolbar_basic.xml @@ -12,8 +12,7 @@ + android:layout_height="?attr/actionBarSize" /> diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 00e1d7bd3..1fda6854d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -26,7 +26,6 @@ الرسائل المباشرة الألسنة تبويق - #%s المشاركات يحتوي على ردود مدبّس @@ -483,4 +482,12 @@ عندما تكون الكلمة أو العبارة أبجدية رقمية فقط ، فلن يتم تطبيقها إلا إذا كانت مطابقة للكلمة بأكملها %1$s • %2$s - + التبويقات المبَرمَجة + تعديل + التبويقات المبَرمَجة + برمجة تبويق + صفّر + اضغط هنا لضبط برمجة التبويق. + خطأ أثناء البحث عن منشور %s + + diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 0263e7ef8..3af47d16f 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -28,7 +28,6 @@ সরাসরি বার্তা ট্যাবগুলি টুট - #%s পোস্টগুলি উত্তরের সাথে পিন করা @@ -440,7 +439,6 @@ %s বাকি %s এ শেষ হবে বন্ধ - <b>%1$d%%</b> %2$s ভোট @@ -505,4 +503,13 @@ পছন্দ %d সম্পাদন +নির্ধারিত টুটগুলি + সম্পাদন + নির্ধারিত টুটগুলি + নির্ধারিত টুট + রিসেট + নির্ধারিত টুট কনফিগার করতে এখানে আলতো চাপুন। + টাস্কি দ্বারা চালিত + %s পোস্ট অনুসন্ধানে ত্রুটি + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 36037dfbe..2b66cd7be 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -23,7 +23,6 @@ Local Federació Toot - #%s Posts Seguits Seguidors @@ -465,7 +464,6 @@ Advertència: %s - <b>%1$d%%</b> %2$s Toot fixat Toot no fixat diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 675cf5fd5..1d0fd5a3d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -26,7 +26,6 @@ Přímé zprávy Panely Toot - #%s Tooty S odpověďmi Připnuté @@ -392,7 +391,6 @@ zbývá %s končí v %s uzavřena - <b>%1$d%%</b> %2$s Hlasovat @@ -466,4 +464,19 @@ Možnost %d Upravit +Plánované tooty + Upravit + Přidat anketu + Plánované tooty + Naplánovat toot + Obnovit + Klepnutím sem nastavíte plánovaný toot. + Vždy rozbalovat tooty označené varováními o obsahu + Celé slovo + Je-li klíčové slovo nebo fráze pouze alfanumerická, bude použita pouze, pokud odpovídá celému slovu + Účty + Hledání selhalo + + Chyba při hledání příspěvku %s + diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 049d88c4c..7c9a6a954 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -278,7 +278,6 @@ Digwyddodd gwall rhwydwaith! Gwiriwch eich cysylltiad a cheisiwch eto! Negeseuon Uniongyrchol Tabiau - #%s Wedi\'i binio Parthau cudd Dim byd yma. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 98bbe6c9c..3dab97d04 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -26,7 +26,6 @@ Direktnachrichten Tabs Unterhaltung - #%s Beiträge mit Antworten Angeheftet @@ -376,7 +375,6 @@ %s verbleibend endet um %s Geschlossen - <b>%1$d%%</b> %2$s Abstimmen @@ -448,4 +446,11 @@ Auswahlmöglichkeit hinzufügen Mehrere Möglichkeiten Möglichkeit %d + Geplante Beiträge + Editieren + Geplante Beiträge + Plane Beitrag + Zurücksetzen + Drücke hier, um den geplanten Beitrag zu konfigurieren. + Dies sind Zeitstempel für Status. Beispiele: \"16s\" oder \"2t\". diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 0f3e4c847..776cc95c2 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -26,7 +26,6 @@ Rektaj mesaĝoj Langetoj Mesaĝo - #%s Mesaĝoj Kun respondoj Alpinglitaj @@ -391,7 +390,6 @@ %s restas finiĝos je %s finiĝita - <b>%1$d%%</b> %2$s Voĉdoni diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ce8427a68..63f507076 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -18,15 +18,14 @@ Se requiere permiso para descargar al almacenamiento. No se pueden adjuntar imágenes y vídeos en el mismo estado. La subida falló. - Error al publicar. + Error al enviar estado. Inicio Notificaciones Local Federada Mensajes Directos Pestañas - Publicación - #%s + Estado Estados Con respuestas Fijado @@ -50,8 +49,8 @@ Ocultar Nada aquí. Nada por aquí. ¡Arrastra hacia abajo para recargar! - %s impulsó tu toot - %s marcó como favorito tu post + %s impulsó tu estado + %s marcó como favorito tu estado %s te siguió Reportar @%s ¿Información adicional? @@ -61,7 +60,7 @@ Favorito Más Redactar - Iniciar sesión + Iniciar sesión con Mastodon Cerrar sesión ¿Seguro que quiere cerrar la sesión de %1$s? Seguir @@ -110,7 +109,7 @@ Abrir como %s Compartir como… Compartir URL… - Compartir contenido… + Compartir estado… Compartir medios a… ¡Enviado! El usuario ya no está bloqueado @@ -143,7 +142,7 @@ Descargar ¿Cancelar petición de amistad? ¿Dejar de seguir esta cuenta? - ¿Eliminar este toot? + ¿Eliminar este estado\? Público: Mostrar en historias públicas Oculto: No mostrar en historias públicas Privado: Sólo visible para seguidores @@ -260,8 +259,8 @@ Tendrá que admitir los seguidores manualmente ¿Guardar borrador? Enviando estado… - Error enviando estado - Enviando estado + Error al enviar el estado + Enviando estados Envío cancelado Una copia del estado se ha guardado en borradores Redactar @@ -370,7 +369,7 @@ Descargando contenido - ¿Borrar y devolver a borradores este toot\? + ¿Borrar y devolver a borradores este estado\? encuestas que han terminado Notificaciones sobre encuestas que han terminado @@ -416,8 +415,6 @@ Abrir autor del impulso Mostrar impulsos - <b>%1$d%%</b> %2$s - Dominios ocultos Dominios ocultos Silenciar %s @@ -445,7 +442,7 @@ La cuenta es de otro servidor. ¿Enviar una copia anónima del reporte\? Mostrar filtro de notificaciones -Mostrar siempre toots marcados con avisos de contenido +Mostrar siempre estados marcados con avisos de contenido Cuentas Error al buscar @@ -463,4 +460,13 @@ Opción %d Editar - +Estados programados + Editar + Estados programados + Programar estado + Reiniciar + Pulsa aquí para configurar un estado programado. + Error al buscar el post %s + +Potenciado por Tusky + diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index e033cacfc..1c1d143e9 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -1,8 +1,8 @@ - + Errorea gertatu da. Eremu hau ezin da hutsik egon. - Domeinu izen okerra. + Domeinu baliogabea sartu da Akatsa saioa hasterakoan. Ez da web nabigatzailerik aurkitu. Identifikatu gabeko baimentza akatsa gertatu da. @@ -27,7 +27,7 @@ Erantzunekin Jarraitzen Jarraitzaileak - Gogokoak + Gogokoenak Isilduak Blokeatuak Eskakizunak @@ -99,22 +99,22 @@ Emoji teklatua %1$s jaisten Lotura kopiatu - Tutaren URL-a partekatu… + Tutaren URLa partekatu… Tuta partekatu… - Partekatu multimedia hona… + Partekatu media hona… Bidalia! - Erabiltzailea ez dago blokeatuta iada. - Iada erabiltzailea ez dago mutututa. + Erabiltzailea desblokeatuta + Erabiltzailea isil gabetua Bidalia! Erantzuna ongi bidali da. - Instantzia hautatu + Zein instantzia\? Zer duzu buruan? - Eduki abisua - Izena + Eduki-abisua + Agertuko den izena Biografia Bilatu… Emaitzarik ez - Erantzuna… + Erantzun… Irudia Goiburua Zer da instantzia? @@ -124,12 +124,12 @@ \n\nInstantzia zure kontua dagoen gunea da, baino beste instantzietako erabiltzaileak zurean egongo balira bezala jarraitu ditzakezu. \n\nInformazio gehiago joinmastodon.org helbidean topatuko duzu. - Multimedia igoera bukatzen + Mediaren igoera bukatzen Igotzen… Jaitsi - Lagun eskaera ezeztatu? - Kontu hau jarraitzeari utzi? - Tuta ezabatu? + Jarraipen-eskaerari uko egin\? + Kontu honi jarraitzeari utzi\? + Tut hau ezabatu\? Publikoa: Istorio publikoetan erakutsi Ezkutukoa: Ez erakutsi istorio publikoetan Pribatua: Jarraitzaileentzat soilik ikusgai @@ -140,53 +140,53 @@ Soinuarekin jakinarazi Bibrazioarekin jakinarazi Led-arekin jakinarazi - Noiz jakinarazi: + Noiz jakinarazi Aipatzen naute Jarraitzen didate Bultzatzen naute - Nire mezuak gogoko ditu + Nire argitarapenak gustokoak izan dira Interfazea - Gaia - Denbora lerroak + Aplikazioaren gaia + Denbora-lerroak Iluna Argia Beltza - Automatikoa + Automatikoa iluntzean Nabigatzailea - Chromeko fitxak erabili - Tut egiteko botoia ezkutatu jaisterakoan. - Denbora-lerro filtroak + Chromeko fitxa pertsonalizatuak erabili + Ezkutatu idazteko botoia mugitzean + Denbora-lerroko iragazkiak Fitxak Erakutsi bultzadak Erakutsi erantzunak - Multimedia aurreikusi - Proxy-a - HTTP Proxy-a - HTTP Proxy-a gaitu - HTTP Proxy-aren zerbitzaria - HTTP Proxy-aren portua - Aurrezarritako ikusgarritasuna - Beti markatu multimedia eduki mingarri gisa - Bidalketak + Jaitsi mediaren aurreikuspenak + Proxya + HTTP proxya + HTTP proxya gaitu + HTTP proxyaren zerbitzaria + HTTP proxyaren portua + Argitarapenen aurrezarritako pribatutasuna + Media eduki-mingarri gisa beti markatu + Argitaratzeak (zerbitzariarekin sinkronizatua) Aukerak sinkronizatzean akatsa Publikoa - Ezkutukoa - Pribatua - Testuaren tamaina - Oso txikia + Zerrendatu gabea + Jarraitzaileak soilik + Status testuaren tamaina + Txikiena Txikia Erdikoa Handia - Oso handia + Handiena Aipamen berriak Aipamen berrien jakinarazpenak - Jarritzaile berriak + Jarraitzaile berriak Jarraitzaile berrien jakinarazpenak Bultzadak - Bultzatutako tuten jakinarazpenak + Bultzatutako zure tuten jakinarazpenak Gogokoak - Gogokoen jakinarazpenak - %s-k aipatu zaitu + Zure tutak gogoko bezala ezartzerakoan jakinarazpenak + %s-(e)k aipatu zaitu %1$s, %2$s, %3$s eta beste %4$d %1$s, %2$s eta %3$s %1$s eta %2$s @@ -205,12 +205,11 @@ Proiektuaren gunea:\n https://accelf.net/yuito - Akats jakinarazpenak eta hobekuntza eskariak:\n - https://github.com/accelforce/Yuito/issues - - Yuito-ren profila - Partekatu edukia - Partekatu lotura + Akatsen berri-emateak eta hobekuntza-eskariak: +\n https://github.com/accelforce/Yuito/issues + Yuitoren profila + Partekatu tutaren edukia + Partekatu tutaren lotura Irudiak Bideoak Eskaera bidalita @@ -277,4 +276,196 @@ Ondorengo edukiak erabiltzailearen informazioa erdizka erakutsi dezake. Profil osoa ikusteko nabigatzailean sakatu. Desainguratu Ainguratu +Sareko errore bat sortu da! Zure konexioa ziurta ezazu berriro, mesedez! + Mezu Zuzenak + Kategoriak + Lotuta + Ezkutuko domeinuak + Programatutako tutak + \@%s + Kilkerrak besterik ez hemen. + Bultzada kendu + Gogokoa kendu + Editatu + Ezabatu eta zirriborroa berriro egin + Ezkutuko domeinuak + Galdeketa gehitu + %s isilarazi + Programatutako tutak + Tuta programatu + Berrezarri + Kategoria gehitu + Estekak + Aipamenak + Bultzadak erakutsi + Gogokoak erakutsi + + Aipamenak + Estekak + Ireki media #%d + + %s bezala ireki + ... bezala partekatu + Media jaisten + Media jaisten + + %s ez dago ezkutatua + + Sakatu hemen programatutako tuta konfiguratzeko. + Tut hau ezabatu eta zirriborro berria egin\? + 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. + Domeinu osoa ezkutatu + + Galdeketak bukatu dira + Iragazkiak + + Erabili sistemaren diseinua + + Hizkuntza + Botentzako erakuslea erakutsi + GIF abatarrak animatu + + Galdeketak + Bukatutako galdeketen jakinarazpenak + + + Tusky %s + Traolak + Traolak + Tusky-k sustatuta + %dh + %dm + %ds + + Beti zabaldu edukien abisuekin markatutako tootak + Elkarrizketak + Gehitu iragazkia + Editatu iragazkia + Kendu + Eguneratu + Hitz osoa + Gako-hitza edo esaldia alfanumerikoa denean bakarrik, hitz osoarekin bat datorrenean bakarrik aplikatuko da + Iragazteko esaldia + + Ezin izan da zerrenda sortu + Ezin izan da zerrendaren izena aldatu + Ezin izan da zerrenda ezabatu + Zerrenda sortu + Zerrenda berrizendatu + Ezabatu zerrenda + Editatu zerrenda + Bilatu jarraitzen dituzun pertsonak + Gehitu kontua zerrendan + Kendu kontua zerrendatik + + Google-ren egungo emoji multzoa + + CC-BY 4.0 + CC-BY-SA 4.0 + + + %1$s Gogoko + %1$s Gogoko + + + + %s Bultzada + %s Bultzada + + + Bultzatuta + Gogokoa + + %1$s + %1$s eta %2$s + %1$s, %2$s eta %3$d gehiago + geienezko %1$d fitxa iritsita + + + Media: %s + Edukiaren abisua: %s + Deskribapenik ez + Birblogeatuta + Gogotuta + Publiko + Zerrendagabetuta + Jarraitzaileak + Zuzena + Inkestatu aukerekin: %1$s, %2$s, %3$s, %4$s; %5$s + + Zerrendaren izena + + Editatu traola + Traola # gabe + Traola + Garbitu + Iragazi + Aplikatu + + Idatzi Toot-a + Idatzi + + Ziur zaude jakinarazpen guztiak betirako garbitu nahi dituzula\? + %s irudiarentzako ekintzak + + %1$s • %2$s + + Boto %s + %s Boto + + %s amaitzen da + itxita + + Botatu + + Botoa eman duzun galdeketa amaitu da + Sortu duzun galdeketa amaitu da + + + Egun %d + %d egun + + + Ordu %d + %d ordu + + + Minutu %d + %d minutu + + + Segundu %d + %d segundu + + + Jarraitu + Itzuli + Eginda + \@%s jakinarazi duzu arrakastaz + Iruzkin gehigarriak + %s-(r)i birbidali + Txostena huts egin du + Egoeren eskuratzea huts egin du + Txostena zure zerbitzariaren moderatzaileari bidaliko zaio. Jarraian, kontu honen zergatia salatzen duzun azalpena eman dezakezu: + Kontua beste zerbitzari batekoa da. Bidali txostenaren kopia anonimatua hara ere\? + Kontuak + Bilaketa huts egin du + + Erakutsi jakinarazpenen iragazkia + + + Inkesta + 5 minutu + 30 minutu + Ordu 1 + 6 ordu + Egun 1 + 3 egun + 7 egun + Gehitu aukera + Aukera anitzak + %d. aukera + Editatu + Errorea agertu da %s mezua bilatzean + diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a611d3ab0..fd18d7566 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -19,260 +19,257 @@ بارگذاری ناموفق بود. خطا در ارسال بوق. خانه - اعلان‌ها + آگاهی‌ها محلی - فدرال + همگانی بوق - پست‌ها + فرسته‌ها با پاسخ‌ - دنبال‌شونده‌ها - دنبال‌کننده‌ها + پی می‌گیرد + پیرو پسندها - کاربرهای بی‌صدا - کاربرهای مسدود شده - درخواست‌های دنبال کردن - ویرایش نمایه شما + کاربران خموش + کاربران مسدود + درخواست‌های پیگیری + ویرایش نمایه‌تان پیش‌نویس‌ها - مجوزها + پروانه‌ها %s تقویت شد - محتوی حساس - پنهان کردن رسانه - برای نمایش کلیک کن - نمایش بیشتر - نمایش کمتر - گسترش دادن - بستن - چیزی اینجا نیست. برای تازه‌سازی، به پایین بکشید! - %s بوق شما را تقویت کرد - %s بوق شما را پسندید - %s شما را دنبال می‌کند + محتوای حسّاس + رسانهٔ پنهان + کلیک برای نمایش + نمایش بیش‌تر + نمایش کم‌تر + گسترش + جمع + چیزی این‌جا نیست. برای تازه‌سازی، به پایین بکشید! + %s بوقتان را تقویت کرد + %s بوقتان را پسندید + %s پیگیرتان شد گزارش @%s - پیام‌های اضافی؟ + توضیحات اضافی؟ پاسخ سریع پاسخ تقویت پسند - بیشتر + بیش‌تر ایجاد - با ماستودون وارد شو + ورود با ماستدون خروج - آیا از خارج شدن از این حساب %1s اطمینان دارید؟ - دنبال کن - لغو دنبال کردن - مسدودسازی + مطمئنید می‌خواهید از حساب %1s خارج شوید؟ + پیگیری + ناپیگیری + انسداد رفع انسداد - تقویت را پنهان کن + نهفتن تقویت‌ها نمایش تقویت‌ها گزارش حذف بوق بوق! - تلاش مجدد - بببند + تلاش دوباره + بستن نمایه ترجیحات ترجیحات حساب پسندها - کاربران بی‌صدا - کاربران مسدود شده - درخواست‌های دنبال کردن + کاربران خموش + کاربران مسدود + درخواست‌های پیگیری رسانه - باز کردن در مرورگر + گشودن در مرورگر افزودن رسانه - عکس بگیر - اشتراک - بی‌صدا - لغو بی‌صدا + گرفتن عکس + هم‌رسانی + خموش + گویا اشاره - رسانه پنهان شود - کشو را باز کن + نهفتن رسانه + گشودن کشو ذخیره ویرایش نمایه ویرایش بازگرداندن پذیرش رد - جستجو - پیش‌نویس - نمایش بوق + جست‌وجو + پیش‌نویس‌ها + نمایانی بوق هشدار محتوا - صفحه کلیک شکلک - درحال دریافت %1$s - رونوشت پیوند + صفحه‌کلید اموجی + در حال بارگیری %1$s + رونوشت از پیوند هم‌رسانی نشانی بوق با… هم‌رسانی بوق با… هم‌رسانی رسانه با… فرستاده شد! کاربر رفع انسداد شد - کاربر رفع بی‌صدا شد + کاربر گویا شد فرستاده شد! - پاسخ با موفق فرستاده شد‌. + پاسخ با موفقیت فرستاده شد‌. کدام نمونه؟ چه خبر؟ هشدار محتوا نام نمایشی - بیوگرافی - جستجو… - نتیجه‌ای نیست + شرح حال + جست‌وجو… + بدون هیچ نتیجه پاسخ … آواتار سرایند - یک نمونه چیست؟ + نمونه چیست؟ در حال اتصال … - آدرس یا دامنه هر نمونه را می‌توانید وارد کنید، مثلا mastodon.social، icosahedron.website، social.tchncs.de، و بیشتر! + این‌جا می‌تواند نشانی یا دامنهٔ هر نمونه را وارد کنید، مثلا mastodon.social، icosahedron.website، social.tchncs.de، و بیش‌تر! \n -\n اگر شما هنوز حساب کاربری ندارید، می‌توانید نام نمونه مورد نظر را وارد کنید از اینجا بپیوندید و حساب کاربری ایجاد کنید. +\n اگر هنوز حساب کاربری ندارید، می‌توانید نام نمونهٔ مورد نظر برای پیوستن را وارد کرده و حساب کاربری بسازید. \n -\n نمونه جایی است که حساب کاربری شما میزبان آن است اما شما به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کنید و آنها را دنبال کنید شما درست مثل اینکه در یکجا باشید. +\n نمونه جایی است که حسابتان میزبانی می‌شود، ولی به راحتی می‌توانید با افراد دیگر در نمونه‌های دیگر ارتباط برقرار کرده و آنان را دنبال کنید، درست مثل این که در یک جا باشید. \n -\n برای اطلاعات بیشتر به اینجا مراجعه کنید joinmastodon.org. +\n برای اطّلاعات بیش‌تر به این‌جا مراجعه کنید joinmastodon.org. پایان بارگذاری رسانه در حال بارگذاری… بارگیری - درخواست دنبال کردن را لغو می‌کنید؟ - لغو دنبال کردن این حساب؟ + لغو درخواست پیگیری؟ + لغو پیگیری این حساب؟ حذف این بوق؟ - عمومی:پست به خط زمانی عمومی - خارج از لیست: در خط زمانی عمومی نشان نده - تنها دنبال‌کنندگان:پست فقط به دنبال‌کنندگان - مستقیم:پست فقط برای کاربران صدا زده شده - ویرایش اعلان‌ها - اعلان‌ها + عمومی: فرستادن به خط زمانی‌های عمومی + فهرست‌نشده: نشان ندادن در خط زمانی‌های عمومی + فقط پیروان: فرستادن فقط به پیروان + مستقیم: فرستادن فقط برای کاربران مورد اشاره + آگاهی‌ها + آگاهی‌ها هشدارها - اعلان با صدا - اعلان با لرزش - اعلان با نور - به من اطلاع بده زمانی که - صدازده‌ها - دنبال‌شده - پست‌های تقویت شده من - پست‌های پسندیده شدهٔ من + آگاهی با صدا + آگاهی با لرزش + آگاهی با چراغ + مرا آگاه کن هنگام + اشاره شدن + دنبال شدن + تقویت بوق‌هایم + پسند بوق‌هایم ظاهر - تم برنامه - خط‌های زمانی + زمینهٔ کاره + خط‌ زمانی‌ها تاریک روشن سیاه - خودکار برحسب غروب خورشید + خودکار در غروب مرورگر استفاده از زبانه‌های سفارشی کروم - پنهان کردن دکمه ایجاد هنگام پیمایش - فیلتر کردن خط زمانی + نهفتن دکمهٔ ایجاد، هنگام پیمایش + پالایش خط زمانی زبانه‌ها نمایش تقویت‌ها نمایش پاسخ‌ها - پیش‌نمایش رسانه را نشان بده - پراکسی - پراکسی HTTP - فعال‌سازی پراکسی HTTP - سرور پراکسی HTTP - درگاه پراکسی HTTP - حریم خصوصی پیش‌فرض پست - همواره رسانه را به عنوان حساس نشانه‌گذاری کن - انتشار (همگادم با کارساز) - ناتوانی در هم‌گام‌سازی تنظیمات + بارگیری پیش‌نمایش رسانه‌ها + پیشکار + پیشکار HTTP + به کار انداختن پیشکار HTTP + کارساز پیشکار HTTP + درگاه پیشکار HTTP + محرمانگی پیش‌گزیدهٔ فرسته + علامت‌گذاری همیشگی رسانه به عنوان حساس + انتشار (همگام با کارساز) + شکست در هم‌گام‌سازی تنظیمات عمومی - فهرست نشده - فقط دنبال‌کنندگان - اندازه متن وضعیت + فهرست‌نشده + فقط پیروان + اندازهٔ متن وضعیت بسیار کوچک کوچک متوسط بزرگ بسیار بزرگ اشاره‌های جدید - اعلان در مورد اشاره‌های جدید - دنبال‌کننده‌های جدید - اعلان درمورد دنبال‌کنندگان جدید + آگاهی‌ها در مورد اشاره‌های جدید + پیروان جدید + آگاهی‌ها درمورد پیروان جدید تقویت‌ها - وقتی بوق‌های شما تقویت شد اعلان بده + آگاهی‌ها هنگام تقویت بوق‌هایتان پسندها - اعلان‌ها وقتی که بوق‌های شما پسندیده شوند - %s شما را صدا زد - %1$s, %2$s, %3$s و %4$d دیگر + آگاهی‌ها هنگام پسندیده شدن بوق‌هایتان + %s به شما اشاره کرد + %1$s, %2$s, %3$s و %4$d نفر دیگر %1$s, %2$s, و %3$s %1$s و %2$s - %d تعاملات جدید - حساب قفل شد + %d برهم‌کنش جدید + حساب قفل‌شده درباره - Yuito یک برنامه آزاد و متن‌باز است که تحت مجوز GNU General Public License Version 3. منتشر شده است.\n - شما می‌توانید مجوز را از اینجا ببینید:\n - https://www.gnu.org/licenses/gpl-3.0.en.html + ییتو نرم‌افزاری آزاد است که تحت نگارش ۳ از پروانهٔ جامع همگانی گنو منتشر شده است. پروانه را می‌توانید از این‌جا ببینید: +\n https://www.gnu.org/licenses/gpl-3.0.en.html - سایت پروژه\n - https://accelf.net/yuito - - گزارش خطا و درخواست ویژگی:\n - https://github.com/accelforce/Yuito/issues - - نمایه Yuito - هم‌رسانی محتوی بوق - هم‌رسانی لینک بوق + پایگاه وب پروژه: +\n https://tusky.app + گزارش مشکلات و درخواست ویژگی‌ها: +\n https://github.com/accelforce/Yuito/issues + نمایهٔ ییتو + هم‌رسانی محتوای بوق + هم‌رسانی پیوند بوق تصویرها فیلم - ‌درخواست دنبال‌کردن فرستاده شد - بدون محتوی + تقاضای پیگیری شد + بدون هیچ محتوا در %dسال در %dر در % dس در %dد در %dث - شما را دنبال می‌کند - همیشه مطلب حساس را نشان بده + پیگیرتان است + نمایش همیشگی محتوای حساس رسانه - پاسخ دادن به @%s - بارگیری بیشتر + در حال پاسخ به @%s + بارگیری بیش‌تر افزودن حساب افزودن حساب ماستدون جدید - سیاهه‌ها - سیاهه‌ها - لیست خط زمانی - پست با حساب %1$s - ناتوان در تنظیم عنوان + فهرست‌ها + فهرست‌ها + خط زمانی فهرست + در حال فرستادن با حساب %1$s + شکست در تنظیم عنوان توصیف برای کم‌بینایان\n(محدودیت نویسه %d) تنظیم عنوان - حذف + برداشتن قفل حساب - به شما امکان می‌دهد بصورت دستی دنبال‌کنندگان را تایید کنید - ذخیره به عنوان پیش‌نویس؟ - فرستادن بوق… + می‌گذارد پیروانتان را به صورت دستی تأیید کنید + ذخیرهٔ پیش‌نویس؟ + در حال فرستادن بوق… خطای فرستادن بوق در حال فرستادن بوق‌ها فرستادن لغو شد - یک رونوشت از بوق در پیش‌نویس‌ها ذخیره شد + رونوشتی از بوق در پیش‌نویس‌هایتان ذخیره شد ایجاد - سرور شما %s هیچ شکلک سفارشی ندارد - به کلیپ بورد کپی شد - قالب شکلک - پیشفرض سیستم - شما ابتدا باید این شکلک‌ها را دریافت کنید - اجرای جستجو… - گستردن/بستن همه وضعیت‌ها - بوق را باز کن - برنامه به شروع مجدد نیاز دارد - شما برای اعمال این تغییرات به شروع مجدد برنامه نیاز دارید - بعدا - شروع مجدد - شکلک‌های پیش‌فرض دستگاه شما تنظیم شدند - شکلک‌ها از اندروید 4.4–7.1 شناخته شده‌اند - شکلک‌های استاندارد ماستدون تنظیم شدند - بارگیری با شکست مواجه شد + نمونه‌تان %s هیچ اموجی سفارشی‌ای ندارد + در تخته‌گیره رونوشت شد + سبک اموجی + پیش‌گزیدهٔ سامانه + نخست باید این اموجی‌ها را بارگیری کنید + اجرای جست‌وجو… + گسترده/جمع کردن تمام وضعیت‌ها + گشودن بوق + نیاز به شروع دوبارهٔ کاره + برای اعمال این تغییرات، نیاز به شروع دوبارهٔ تاسکی دارید + بعداً + شروع دوباره + مجموعهٔ اموجی پیش‌گزیدهٔ افزاره‌تان + اموجی‌های اندروید ۴.۴ تا ۷.۱ + مجموعهٔ اموجی استاندارد ماستدون + شکست در بارگیری بات - %1$s جابجا شد به: + %1$s منتقل شد به: تقویت برای مخاطب اصلی لغو تقویت - Yuito شامل کد و دارایی‌های پروژه‌های منبع‌باز زیر است: - تحت مجوز آپاچی (رونوشت در ادامه) - متاداده نمایه + ییتو شامل کد و دارایی‌هایی از پروژه‌های آزاد زیر است: + تحت پروانهٔ آپاچی (رونوشت در ادامه) + فرادادهٔ نمایه افزودن داده برچسب - محتوی + محتوا استفاده از زمان مطلق اطلاعات زیر ممکن است به طور ناقص نمایه کاربر را نشان دهد. برای دیدن نمایه کامل در مرورگر، لمس کنید. برداشتن سنجاق @@ -280,83 +277,82 @@ یک خطای شبکه رخ داد! لطفا اتصال خود را بررسی و دوباره تلاش کنید! پیام‌های مستقیم زبانه‌ها - #%ث - سنجاق‌شده‌ها + سنجاق‌شده دامنه‌های پنهان \@%s - چیزی اینجا نیست. - حذف بازبوق - حذف پسند - پاک کردن و بازنویسی + چیزی این‌جا نیست. + برداشتن تقویت + برداشتن پسند + حذف و بازنویسی دامنه‌های پنهان افزودن نظرسنجی - بی‌صدا کردن %s - افزوده زبانه + خموشی %s + افزودن زبانه پیوندها اشاره‌ها - هشتگ‌ها - بازکردن حساب بازبوق کننده - نمایش بازبوق‌ها - نمایش پسندیده‌ها + برچسب‌ها + گشودن تقویتگر + نمایش تقویت‌ها + نمایش پسندها - هشتگ‌ها + برچسب‌ها اشاره‌ها پیوندها - بازکردن رسانه #%d + گشودن رسانهٔ #%d - باز کردن به عنوان %s + گشودن به عنوان %s هم‌رسانی به عنوان … بارگیری رسانه در حال بارگیری رسانه %s ناپنهان - می‌خواهید این بوق را پاک و بازنویسی کنید؟ - پنهان کردن تمام دامنه + حذف و بازنویسی این بوق؟ + نهفتن تمام دامنه - نظرسنجی‌های پایان یافته - صافی‌ها + پایان نظرسنجی‌ها + پالایه‌ها - استفاده از طرح سیستم + استفاده از طراحی سامانه زبان - نمایش نشان برای بات‌ها - پویانمایی آواتار gif + نمایش نشانگر برای بات‌ها + پویانمایی آواتارهای جیف نظرسنجی‌ها - اعلان‌ها درباره نظرسنجی‌هایی که پایان یافته‌اند + آگاهی‌ها در مورد نظرسنجی‌های پایان‌یافته - Tusky (تاسکی) %s + تاسکی %s %dسال %dر %dس %dد %dث - همواره بوق‌هایی که دارای محتوای حساس هستند را گسترش بده - خط زمانی عمومی - گفتگوها - افزودن صافی - ویرایش صافی - پاک کردن - به‌روزرسانی - تمام کلمه - ناتوانی در ایجاد سیاهه - ناتوانی در تغییر نام سیاهه - ناتوانی در حذف سیاهه - ایجاد یک سیاهه - تغییر نام سیاهه - حذف سیاهه - ویرایش سیاهه - جستجو بین افرادی که دنبال می‌کنید - افزودن حساب به سیاهه - حذف حساب از سیاهه + گسترش همیشگی بوق‌های علامت خورده با هشدار محتوا + خط زمانی‌های عمومی + گفت‌وگوها + افزودن پالایه + ویرایش پالایه + برداشتن + به‌روز رسانی + تمام واژه + ناتوانی در ایجاد فهرست + ناتوانی در تغییر نام فهرست + ناتوانی در حذف فهرست + ایجاد یک فهرست + تغییر نام فهرست + حذف فهرست + ویرایش فهرست + جست‌وجو برای دنبال‌شوندگانتان + افزودن حساب به فهرست + حذف حساب از فهرست - مجموعه شکلک‌های جاری گوگل + مجموعهٔ اموجی کنونی گوگل - CC-BY 4.0 - CC-BY-SA 4.0 + نگارش ۴ CC-BY + نگارش ۴ CC-BY-SA %1$s پسند @@ -364,56 +360,56 @@ - %s بازبوق - %s بازبوق + %s تقویت + %s تقویت - بازبوق شده توسط - پسننده‌شده توسط + تقویت به دست + پسندیده به دست %1$s %1$s و %2$s - %1$s، %2$s و %3$d بیشتر - به بیشینه %1$d زبانه رسید + %1$s، %2$s و %3$d نفر دیگر + رسیده به بیشینهٔ %1$d زبانه رسانه: %s هشدار محتوا: %s - بدون توضیحات + بدون هیج توضیحی بازبوقیده پسندیده عمومی فهرست‌نشده - دنبال‌کنندگان + پیروان مستقیم نظرسنجی با انتخاب‌ها: %1$s، %2$s، %3$s، %4$s؛ %5$s - نام سیاهه + نام فهرست - ویرایش هشتگ - هشتگ بدون # - هشتگ - پاک کردن - صافی + ویرایش برچسب + برچسب بدون # + برچسب + پاک‌سازی + پالایش اعمال ایجاد بوق ایجاد - مطمئنید که به طور دائمی می‌خواهید اعلان‌ها را پاک کنید؟ - کنش‌ها برای تصاویر %s + مطمئنید می‌خواهید تمام آگاهی‌هایتان را برای همیشه پاک کنید؟ + کنش‌ها برای تصویر %s - %s رای - %s رای + %s رأی + %s رأی - %s باقی مانده - پایان یافته در %s - بسته شده - رای + %s مانده + پایان در %s + بسته + رأی - یک نظرسنجی که در آن رای داده‌اید پایان یافته است - یک نظرسنجی ساخته‌اید پایان یافته است + یک نظرسنجی که در آن رأی دادید، تمام شد + یک نظرسنجی که ساختید، تمام شد %d روز @@ -433,17 +429,17 @@ ادامه - قبل - اتمام - با موفقیت گزارش شد @%s - نظرات بیشتر + بازگشت + تمام + \@%s با موفّقیت گزارش شد + نظرات اضافی هدایت به %s - ناتوانی در گزارش - ناتوانی در دریافت وضعیت‌ها + شکست در گزارش + شکست در واکشی وضعیت‌ها حساب‌ها - ناتوانی در جستجو + شکست در جست‌وجو - نمایش فیلتر اعلانات + نمایش پالایهٔ آگاهی‌ها نظرسنجی @@ -455,8 +451,24 @@ ۳ روز ۷ روز افزودن گزینه - گزینه‌های چندگانه - گزینه %d + چندگزینه‌ای + گزینهٔ %d ویرایش - +بوق‌های زمان‌بندی‌شده + ویرایش + بوق‌های زمان‌بندی‌شده + زمان‌بندی بوق + بازنشانی + برای پیکربندی بوق زمان‌بندی‌شده، این‌جا را بزنید. + مطمئنید می‌خواهید تمام %s را مسدود کنید؟ محتوای آن دامنه را در هیچ‌یک از خط زمانی‌ها یا در آگاهی‌هایتان نخواهید دید. پیروانتان از آن دامنه، برداشته خواهند شد. + هنگامی که کلیدواژه یا عبارت، فقط حروف‌عددی باشد، فقط اگر با تمام واژه مطابق باشد، اعمال خواهد شد + عبارت پالایش + + %1$s • %2$s + گزارش به ناظم‌های کارسازتان ارسال خواهد شد. می‌توانید توضیحی در باب چرایی گزارش این حساب در زیر بنویسید: + این حساب از کارسازی دیگر است. رونوشتی ناشناس از گزارش، به آن‌جا نیز ارسال شود؟ + خطا در یافتن فرستهٔ %s + +قدرت‌گرفته از تاسکی + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c6f58c334..bb5175517 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -26,7 +26,6 @@ Messages directs Onglets Pouet - #%s Pouets Pouets & réponses Épinglés @@ -364,17 +363,17 @@ Direct Nom de la liste -Modifier les hashtags - Hastags sans # +Édition des hashtags + Hashtags sans # Hashtag -Nettoyer +Effacer Filtrer Appliquer - Ecrire un pouet - Ecrire + Écrire un pouet + Écrire Afficher l\'indicateur de robots Nettoyer toutes les notifications de façon permanente \? @@ -393,7 +392,7 @@ Sondages - Les sondages sont clos + Les sondages sont terminés Notifications pour les sondages terminés @@ -421,7 +420,6 @@ Actions pour l’image %s %1$s • %2$s - <b>%1$d%%</b> %2$s Un sondage auquel vous avez participé vient de se terminer @@ -469,4 +467,13 @@ Choix %d Éditer - +Pouets planifiés + Éditer + Pouets planifiés + Planifier le pouet + Réinitialiser + Appuyez ici pour configurer le pouet planifié. + Erreur lors de la recherche du post %s + +Propulsé par Tusky + diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 000000000..54d04dc9a --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,29 @@ + +मॅस्टोडौन के साथ लॉगिन करें + पसंदीदा + ड्राफ्ट + लॉग आउट + पसंद + खाता प्राथमिकताएं + प्रोफाइल एडिट करें + खोज + के बारे में + उत्तरों के साथ + फॉलोअर्स + उत्तर दें + फॉलो + अनफॉलो + ब्लॉक + अनब्लॉक + बूस्ट छिपाएं + बूस्ट दिखाएं + रिपोर्ट करें + एडिट करें + डिलीट करें + डिलीट एवं रिड्राफ्ट करें + टूट करें + टूट! + पुनः प्रयास करें + बंद करें + प्रोफाइल + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5eaa41764..9b85ab5df 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -26,7 +26,6 @@ Közvetlen üzenetek Fülek Tülk - #%s Tülkölés Válaszokkal Rögzített @@ -413,7 +412,6 @@ %s maradt vége %s véget ért - <b>%1$d%%</b> %2$s Szavazás @@ -467,4 +465,12 @@ Válasz %d Szerkesztés +Időzített tülkök + Szerkesztés + Időzített tülkök + Tülk Időzítése + Visszaállítás + Ide nyúlj az időzített tülkök beállításához. + Nem találjuk ezt a posztot %s + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 20d01ae59..f2f8f77ce 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -26,7 +26,6 @@ Messaggi Diretti Schede Toot - #%s Post Con risposte Fissati in alto diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a4848994a..cdbfb3a49 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -25,7 +25,6 @@ ダイレクトメッセージ タブ スレッド - #%s 投稿 投稿と返信 ピン留め diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 05caa435b..3b0ce27cc 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -28,7 +28,6 @@ 다이렉트 메시지 - #%s 게시물 툿과 답장 고정됨 @@ -450,7 +449,6 @@ %s 남음 %s에 종료 마감됨 - %1$d%% %2$s 투표 diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml new file mode 100644 index 000000000..115777223 --- /dev/null +++ b/app/src/main/res/values-ml/strings.xml @@ -0,0 +1,62 @@ + +മസ്റ്റഡോൺ വഴി പ്രവേശിക്കുക + എന്താണ് ഒരു ഇൻസ്റ്റൻസ്\? + + പ്രിയപ്പെട്ടവ + കരടുകൾ + പുറത്തിറങ്ങുക + മുൻഗണനകൾ + അക്കൗണ്ട് മുൻഗണനകൾ + പ്രൊഫൈൽ തിരുത്തുക + തിരയുക + വിവരം + പട്ടികകൾ + പട്ടികകൾ + ഒരു പിഴവ് സംഭവിച്ചിരിക്കുന്നു. + ഒരു നെറ്റ്‌വർക്ക് പിഴവ് സംഭവിച്ചിരിക്കുന്നു! ദയവായി താങ്കളുടെ കണക്ഷൻ പരിശോധിച്ചിട്ട് വീണ്ടും ശ്രമിക്കൂ! + ഇത് ശൂന്യമാവാൻ പാടില്ല. + അസാധുവായ ഡൊമൈൻ നൽകിയിരിക്കുന്നു + ആ ഇൻസ്റ്റൻസുമായി ആധികാരികത ഉറപ്പുവരുത്തുന്നതിൽ പരാജയപ്പെട്ടിരിക്കുന്നു. + ഉപയോഗിക്കാനായി ഒരു വെബ് ബ്രൗസർ കണ്ടെത്താനായില്ല. + അജ്ഞാതമായ ഒരു ആധികാരികതാപിഴവ് സംഭവിച്ചിരിക്കുന്നു. + ആധികാരികത ഉറപ്പുവരുത്താനായില്ല. + ഒരു പ്രവേശന ടോക്കൺ ലഭ്യമാക്കുന്നതിൽ പരാജയപ്പെട്ടു. + ഈ സ്റ്റാറ്റസ് വളരെ നീളമേറിയതാണ്! + ഫയൽ 8 എംബിയേക്കാളും ചെറുതായിരിക്കണം. + ചലച്ചിത്ര ഫയലുകൾ 40 എംബിയേക്കാളും ചെറുതായിരിക്കണം. + ഇത്തരം ഫയൽ അപ്‌ലോഡ് ചെയ്യാൻ സാധിക്കില്ല. + ഈ ഫയൽ തുറക്കാനായില്ല. + മീഡിയ വായിക്കുവാനുള്ള അനുമതി ആവശ്യമാണ്. + മീഡിയ സംഭരിക്കുവാനുള്ള അനുമതി ആവശ്യമാണ്. + ചിത്രങ്ങളും ചലച്ചിത്രങ്ങളും ഒരുമിച്ച് ഒരു സ്റ്റാറ്റസിലേക്ക് ചേർക്കാനാവില്ല. + അപ്‌ലോഡ് പരാജയപ്പെട്ടു. + ടൂട്ട് അയയ്ക്കുന്നതിൽ പിഴവ്. + + പൂമുഖം + അറിയിപ്പുകൾ + ലോക്കൽ + ഫെഡറേറ്റഡ് + നേരേയുള്ള സന്ദേശങ്ങൾ + ടാബുകൾ + ടൂട്ട് + പോസ്റ്റുകൾ + മറുപടികളോടൊപ്പം + പിൻ ചെയ്തത് + പിന്തുടരലുകൾ + പിന്തുടരുന്നവർ + നിശ്ശബ്ദരാക്കിയ ഉപയോക്താക്കൾ + തടയപ്പെട്ട ഉപയോക്താക്കൾ + പിന്തുടരാനുള്ള അപേക്ഷകൾ + പ്രൊഫൈൽ തിരുത്തുക + മറുപടി + ബൂസ്റ്റ് + ബൂസ്റ്റ് പിൻവലിക്കുക + ഇഷ്ടപ്പെട്ടവ + ഇഷ്ടപ്പെട്ടവയിൽ നിന്നും കളയുക + കൂടുതൽ + പിന്തുടരുക + പിന്‍തുടരുന്നത് അവസാനിപ്പിക്കുക + വീണ്ടും ശ്രമിക്കുക + പിന്‍തുടരുവാനുള്ള അഭ്യര്‍ത്ഥനകള്‍ + മറയ്ക്കപ്പെട്ട ഡൊമൈനുകൾ + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 372329bb1..6ab982f7b 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -27,10 +27,9 @@ @color/window_background_dark @color/custom_tab_toolbar_dark - @color/toolbar_background_dark - @color/toolbar_icon_dark + @color/toolbar_icon_dark @style/TuskyImageButton.Dark - @drawable/ic_reblog_dark_18dp + @drawable/ic_reblog_dark_24dp @drawable/reblog_inactive_dark @drawable/reblog_private_dark @drawable/reblog_unleakable_dark @@ -45,7 +44,7 @@ @drawable/status_divider_dark @drawable/conversation_thread_line_dark @color/tusky_blue - @drawable/tab_page_margin_dark + @color/tab_page_margin_dark @color/color_background_dark @color/toolbar_icon_dark @color/account_toolbar_icon_collapsed_dark diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b3bb35606..262d0184f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -26,7 +26,6 @@ Directe berichten Tabs Toot - #%s Toots Met reacties Vastgezet @@ -390,7 +389,6 @@ %s over eindigt op %s gesloten - <b>%1$d%%</b> %2$s Stemmen diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 06e1e74b1..7f490ffbb 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -28,7 +28,6 @@ Direktemeldinger Faner Toot - #%s Toots Med svar Festet @@ -410,7 +409,6 @@ %s igjen avsluttes %s stengt - <b>%1$d%%</b> %2$s Stem @@ -504,4 +502,13 @@ Valg %d Endre - +Planlagte toots + Rediger + Planlagte toots + Planlegg toot + Tilbakestill + Klikk her for å konfigurere planlagt toot. + Det oppsto en feil under henting av %s + +Drevet av Tusky + diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index ac34cba3b..c03115443 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -274,7 +274,6 @@ Local Messatges dirèctes Onglets - #%s Penjats \@%s Pas res aicí. @@ -405,7 +404,6 @@ %1$s • %2$s - <b>%1$d%%</b> %2$s Un sondatge ont avètz votat es acabat Un sondatge qu’avètz creat es acabat @@ -474,4 +472,13 @@ Opcion %d Modificar - +Tuts planificats + Modificar + Tuts planificats + Planificar de tuts + Escafar + Tocatz aquí per configurar los tuts planificats. + Error en cercant la publicacion %s + +Propulsat per Tusky + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index baa25bcae..88cde261e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -288,10 +288,9 @@ Otwórz jako %s Udostępnij jako … Zakładki - #%s Ukryte domeny \@%s - Usuń podbicie + Cofnij podbicie Ukryte domeny Dodaj głosowanie Wycisz %s @@ -424,7 +423,6 @@ Zostało %s kończy się %s zakończone - <b>%1$d%%</b> %2$s Głosuj @@ -481,4 +479,13 @@ Opcja %d Edytuj +Zaplanowane wpisy + Edytuj + Zaplanowane wpisy + Zaplanuj wpis + Resetuj + Dotknij tutaj, żeby skonfigurować zaplanowany wpis. + Napędzane przez Tusky + Błąd przy wyszukiwaniu wpisu %s + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 41d13e4f4..0afc04a18 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -294,7 +294,6 @@ Seguidores Ocorreu um erro de conexão! Por favor, verifique sua internet e tente novamente! - #%s Fixado \@%s Expandir @@ -400,7 +399,6 @@ %s restando termina em %s Terminou - <b>%1$d%%</b> %2$s Votar @@ -438,7 +436,7 @@ Falha na denúncia Falha ao carregar toots A denúncia será enviada para o seu administrador da instância. Você pode explicar por que você denunciou a conta: - A conta está em outra instância. Enviar uma cópia anonimizada da denúncia também\? + A conta está em outra instância. Enviar uma cópia anônima da denúncia também\? Instâncias bloqueadas Instâncias bloqueadas @@ -469,4 +467,12 @@ Opção %d Editar +Toots agendados + Editar + Toots agendados + Agendar toot + Cancelar + Toque aqui para agendar + Erro ao pesquisar %s + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b9468d8f4..cefb327cf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -28,7 +28,6 @@ Личные сообщения Вкладки Обсуждение - #%s Посты Посты и ответы Закреплённые @@ -405,11 +404,10 @@ Закрепить - <b>%1$s</b> Понравилось - <b>%1$s</b> Понравилось - <b>%1$s</b> Понравилось - <b>%1$s</b> Понравилось - + %1$s Понравилось + %1$s Понравились + %1$s Понравилось + <b>%s</b> Продвинул(а) @@ -482,9 +480,6 @@ %s завершится %s завершён - - - <b>%1$d%%</b> %2$s Голосовать @@ -540,4 +535,12 @@ Вариант %d Изменить +Отложенные записи + Изменить + Отложенные записи + Отложить запись + Сброс + Нажмите для выбора времени отправки. + Ошибка при поиске сообщения / ний + diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index e83aaf62a..ba78d6824 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -27,7 +27,6 @@ Neposredna Sporočila Zavihki Tut - #%s Objave Z odgovori Pripeto @@ -36,7 +35,7 @@ Priljubljene Utišani uporabniki Blokirani uporabniki - Zahteve za sledenje + Prošnje za sledenje Uredi svoj profil Osnutki Licence @@ -89,7 +88,7 @@ Priljubljeni Utišani uporabniki Blokirani uporabniki - Zahteve za sledenje + Prošnje za sledenje Mediji Odpri v brskalniku Dodaj medij @@ -417,7 +416,6 @@ še %s se konča ob %s zaprto - <b>%1$d%%</b> %2$s Glasovanje @@ -518,4 +516,13 @@ Izbira %d Uredi - +Napovedani tuti + Uredi + Napovedani tuti + Ponastavi + Napovej tut + Dotaknite se tukaj, da nastavite napovedan tut. + Napaka pri iskanju objave %s + +Poganja ga Tusky + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index e64e67a03..828c2e899 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -26,7 +26,6 @@ Direkta meddelanden Flikar Toot - #%s Inlägg Med svar Fastnålade @@ -388,7 +387,6 @@ %s kvar avslutas %s stängd - <b>%1$d%%</b> %2$s Rösta @@ -465,4 +463,12 @@ Val %d Redigera +Schemalagda toots + Redigera + Schemalagda toots + Schemalägg toot + Återställ + Knacka här för att konfigurera schemalagd toot. + Fel vid uppslagning av status %s + diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 7040478ab..8bf55ac78 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -1,4 +1,4 @@ - + பிழை ஏற்பட்டது. இது காலியாக இருக்க கூடாது. @@ -18,14 +18,14 @@ பதிவேற்றம் தோல்வியுற்றது. முகப்பு அறிவிப்புகள் - அருகாமயில் + அருகாமையில் ஒருங்கிணைந்த பதிவுகள் பதிலளிக்கபட்டவை பின்பற்றுகிறீர் - பின்பற்றுபவர்கள் - பிடித்தவைகள் - ஒலி நீக்கபட்ட பயனர்கள் + தொடர்பவர்கள் + விரும்பியவை + ஒதுக்கப்பட்ட பயனர்கள் தடைசெய்யபட்ட பயனர்கள் பின்பற்ற கோரிக்கை சுயவிவரத்தை திருத்த @@ -259,4 +259,16 @@ கீழுள்ள தகவல் பயனரின் சுயவிவரத்தின் பிரதிபலிப்பு முழுமையடையாது. முழு சுயவிவரத்தை உலாவில் திறக்க அழுத்தவும். விடுவி பொருத்து - +கணக்கரின் முன்னுரிமைகள் + "பிணைய பிழை ஏற்பட்டது! உங்கள் இணைப்பைச் சரிபார்த்து மீண்டும் முயற்சிக்கவும்!" + காணொளி 40MB க்கும் குறைவாக இருக்க வேண்டும். + டூத் அனுப்ப இயலவில்லை + + நேரடி தகவல் + பட்டைகள் + பொருத்தப்பட்டது + 1 நாள் + 3 நாட்கள் + 7 நாட்கள் + விருப்பத்தைச் சேர் + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2f80e6b34..496716f5a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -26,7 +26,6 @@ Direkt Mesajlar Sekmeler İleti - #%s İletiler Yanıtlar Sabitlenmiş diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 716ea0fd9..f9d99ce66 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -27,7 +27,6 @@ 私信 标签页 嘟文 - #%s 嘟文 嘟文和回复 已置顶 @@ -459,9 +458,6 @@ 剩余 %s %s 结束 已结束 - - - <b>%1$d%%</b> %2$s 投票 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index c8dd30a4d..d3b349a48 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -27,7 +27,6 @@ 私信 標籤頁 嘟文 - #%s 嘟文 嘟文和回覆 已置頂 @@ -454,10 +453,7 @@ 剩餘 %s %s 結束 已結束 - - - <b>%1$d%%</b> %2$s - + 投票 你參與的投票已結束 diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index c8dd30a4d..d3b349a48 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -27,7 +27,6 @@ 私信 標籤頁 嘟文 - #%s 嘟文 嘟文和回覆 已置頂 @@ -454,10 +453,7 @@ 剩餘 %s %s 結束 已結束 - - - <b>%1$d%%</b> %2$s - + 投票 你參與的投票已結束 diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml index d07366ef0..a6926c9c2 100644 --- a/app/src/main/res/values-zh-rSG/strings.xml +++ b/app/src/main/res/values-zh-rSG/strings.xml @@ -27,7 +27,6 @@ 私信 标签页 嘟文 - #%s 嘟文 嘟文和回复 已置顶 @@ -459,9 +458,6 @@ 剩余 %s %s 结束 已结束 - - - <b>%1$d%%</b> %2$s 投票 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 80b7cc935..5c18ca0aa 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -27,7 +27,6 @@ 私信 標籤頁 嘟文 - #%s 嘟文 嘟文和回覆 已置頂 @@ -453,10 +452,7 @@ 剩餘 %s %s 結束 已結束 - - - <b>%1$d%%</b> %2$s - + 投票 你參與的投票已結束 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 34aeb80cf..f91fea547 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -12,8 +12,7 @@ - - + @@ -29,7 +28,7 @@ - + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index cdd9ff025..378090440 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -19,7 +19,6 @@ #ffffff #d9e1e8 #9baec8 - #4c5368 #d9e1e8 #444b5d #2f3441 @@ -46,7 +45,6 @@ #CC000000 #3c3c3c #5f636f - #f6f7f7 #7C000000 #BFBFBF #b0b0b0 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index a5ba6214d..f8bbe3d48 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -40,8 +40,13 @@ 4.5dp 3dp 160dp + 14dp + 5dp 12dp + + 72dp + 108dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 79ba6a22e..4a69fdb42 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -1,7 +1,7 @@ - Yuito - https://accelf.net/yuito + https://accelf.net/yuito + %1$s %2$s oauth2redirect com.keylesspalace.tusky.PREFERENCES @@ -9,6 +9,7 @@ <b>%1$s</b><br>%2$s \@ # + #%s :%s: @@ -130,5 +131,5 @@ 604800 - + <b>%1$d%%</b> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f3b8e238..db5818601 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,7 +30,6 @@ Direct Messages Tabs Toot - #%s Notestock Posts With replies @@ -297,6 +296,7 @@ About Yuito %s + Powered by Tusky Yuito is free and open-source software. It is licensed under the GNU General Public License Version 3. You can view the license here: https://www.gnu.org/licenses/gpl-3.0.en.html @@ -510,8 +510,6 @@ %s left ends at %s closed - - <b>%1$d%%</b> %2$s Vote @@ -564,5 +562,6 @@ Multiple choices Choice %d Edit + Error looking up post %s diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c19c81782..18ef8584c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -63,6 +63,8 @@ @color/color_primary_dark_light + @style/Widget.MaterialComponents.AppBarLayout.Surface + @color/color_background_light @color/color_primary_light @color/window_background_light @@ -77,10 +79,9 @@ @color/window_background_light @color/custom_tab_toolbar_light - @color/toolbar_background_light - @color/toolbar_icon_light + @color/toolbar_icon_light @style/TuskyImageButton.Light - @drawable/ic_reblog_light_18dp + @drawable/ic_reblog_light_24dp @drawable/reblog_inactive_light @drawable/reblog_private_light @drawable/reblog_unleakable_light @@ -98,7 +99,7 @@ @drawable/conversation_thread_line_light @color/tusky_blue - @drawable/tab_page_margin_light + @color/tab_page_margin_light @color/color_primary_dark_light @color/toolbar_icon_dark @@ -159,9 +160,6 @@ ?attr/status_text_medium true 3dp - ?attr/tab_icon_selected_tint - ?android:attr/textColorSecondary - ?attr/tab_icon_selected_tint