Merge remote-tracking branch 'tuskyapp/develop'
This commit is contained in:
commit
5503c801c1
|
@ -102,12 +102,12 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
|||
}
|
||||
}
|
||||
|
||||
ext.lifecycleVersion = "2.1.0"
|
||||
ext.roomVersion = '2.2.3'
|
||||
ext.retrofitVersion = '2.6.0'
|
||||
ext.lifecycleVersion = "2.2.0"
|
||||
ext.roomVersion = '2.2.4'
|
||||
ext.retrofitVersion = '2.7.1'
|
||||
ext.okhttpVersion = '4.3.1'
|
||||
ext.glideVersion = '4.10.0'
|
||||
ext.daggerVersion = '2.25.3'
|
||||
ext.daggerVersion = '2.26'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
|
@ -119,9 +119,9 @@ repositories {
|
|||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.2.0-rc01"
|
||||
implementation "androidx.core:core-ktx:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat:1.2.0-alpha02"
|
||||
implementation "androidx.fragment:fragment-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.2"
|
||||
implementation "androidx.browser:browser:1.2.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
|
@ -131,8 +131,9 @@ dependencies {
|
|||
implementation "androidx.sharetarget:sharetarget:1.0.0-rc01"
|
||||
implementation "androidx.emoji:emoji:1.0.0"
|
||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||
|
@ -140,7 +141,7 @@ dependencies {
|
|||
implementation "androidx.room:room-rxjava2:$roomVersion"
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
|
||||
implementation "com.google.android.material:material:1.1.0-rc01"
|
||||
implementation "com.google.android.material:material:1.1.0"
|
||||
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
|
||||
|
|
|
@ -27,6 +27,7 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Px
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
@ -34,7 +35,6 @@ import androidx.core.app.ActivityOptionsCompat
|
|||
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
|
||||
|
@ -76,7 +76,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: AccountViewModel
|
||||
private val viewModel: AccountViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private val accountFieldAdapter = AccountFieldAdapter(this)
|
||||
|
||||
|
@ -116,9 +116,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
loadResources()
|
||||
makeNotificationBarTransparent()
|
||||
setContentView(R.layout.activity_account)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[AccountViewModel::class.java]
|
||||
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
|
|||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(TuskyApplication.localeManager.setLocale(base));
|
||||
super.attachBaseContext(TuskyApplication.getLocaleManager().setLocale(base));
|
||||
}
|
||||
|
||||
protected boolean requiresLogin() {
|
||||
|
|
|
@ -178,11 +178,13 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
// https://mastodon.foo.bar/@User
|
||||
// https://mastodon.foo.bar/@User/43456787654678
|
||||
// https://pleroma.foo.bar/users/User
|
||||
// https://pleroma.foo.bar/users/43456787654678
|
||||
// https://pleroma.foo.bar/notice/43456787654678
|
||||
// https://pleroma.foo.bar/users/9qTHT2ANWUdXzENqC0
|
||||
// https://pleroma.foo.bar/notice/9sBHWIlwwGZi5QGlHc
|
||||
// https://pleroma.foo.bar/objects/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
||||
// https://friendica.foo.bar/profile/user
|
||||
// https://friendica.foo.bar/display/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
||||
// https://misskey.foo.bar/notes/83w6r388br (always lowercase)
|
||||
// https://mastodon.foo.bar/users/User/statuses/000000000000000000
|
||||
// https://new.misskey.foo.bar/notes/012789abyz
|
||||
// username@example.com
|
||||
fun looksLikeMastodonUrl(urlString: String): Boolean {
|
||||
val uri: URI
|
||||
|
@ -200,14 +202,15 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
|||
|
||||
val path = uri.path
|
||||
return path.matches("^/@[^/]+$".toRegex()) ||
|
||||
path.matches("^/users/[^/]+$".toRegex()) ||
|
||||
path.matches("^/@[^/]+/\\d+$".toRegex()) ||
|
||||
path.matches("^/notice/\\d+$".toRegex()) ||
|
||||
path.matches("^/users/\\w+$".toRegex()) ||
|
||||
path.matches("^/notice/[a-zA-Z0-9]+$".toRegex()) ||
|
||||
path.matches("^/objects/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/users/[^/]+/statuses/[0-9]+$".toRegex()) ||
|
||||
path.matches("^/notes/[a-z0-9]+$".toRegex()) ||
|
||||
path.matches("^/display/[-a-f0-9]+$".toRegex()) ||
|
||||
path.matches("^/profile/\\w+$".toRegex()) ||
|
||||
path.matches("^/users/[^/]+/statuses/[0-9]+$".toRegex()) ||
|
||||
path.matches("^[^@]+@[^@]+$".toRegex())
|
||||
|
||||
}
|
||||
|
||||
enum class PostLookupFallbackBehavior {
|
||||
|
|
|
@ -19,7 +19,6 @@ import android.Manifest
|
|||
import android.app.Activity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
|
@ -34,6 +33,7 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.viewModels
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
|
@ -69,7 +69,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: EditProfileViewModel
|
||||
private val viewModel: EditProfileViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private var currentlyPicking: PickType = PickType.NOTHING
|
||||
|
||||
|
@ -90,8 +90,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
setContentView(R.layout.activity_edit_profile)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[EditProfileViewModel::class.java]
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.run {
|
||||
setTitle(R.string.title_edit_profile)
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.emoji.text.EmojiCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.evernote.android.job.JobManager;
|
||||
import com.keylesspalace.tusky.db.AccountManager;
|
||||
import com.keylesspalace.tusky.db.AppDatabase;
|
||||
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;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.AndroidInjector;
|
||||
import dagger.android.DispatchingAndroidInjector;
|
||||
import dagger.android.HasAndroidInjector;
|
||||
|
||||
public class TuskyApplication extends Application implements HasAndroidInjector {
|
||||
@Inject
|
||||
DispatchingAndroidInjector<Object> androidInjector;
|
||||
|
||||
@Inject
|
||||
NotificationPullJobCreator notificationPullJobCreator;
|
||||
|
||||
private AppDatabase appDatabase;
|
||||
private AccountManager accountManager;
|
||||
|
||||
private ServiceLocator serviceLocator;
|
||||
|
||||
public static LocaleManager localeManager;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
initSecurityProvider();
|
||||
|
||||
appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "tuskyDB")
|
||||
.allowMainThreadQueries()
|
||||
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5,
|
||||
AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8,
|
||||
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
|
||||
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13,
|
||||
AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16,
|
||||
AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19,
|
||||
AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21)
|
||||
.build();
|
||||
accountManager = new AccountManager(appDatabase);
|
||||
serviceLocator = new ServiceLocator() {
|
||||
@Override
|
||||
public <T> T get(Class<T> clazz) {
|
||||
if (clazz.equals(AccountManager.class)) {
|
||||
//noinspection unchecked
|
||||
return (T) accountManager;
|
||||
} else if (clazz.equals(AppDatabase.class)) {
|
||||
//noinspection unchecked
|
||||
return (T) appDatabase;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown service " + clazz);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AutoDisposePlugins.setHideProxies(false);
|
||||
|
||||
initAppInjector();
|
||||
initEmojiCompat();
|
||||
initNightMode();
|
||||
|
||||
JobManager.create(this).addJobCreator(notificationPullJobCreator);
|
||||
|
||||
}
|
||||
|
||||
protected void initSecurityProvider() {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
localeManager = new LocaleManager(base);
|
||||
super.attachBaseContext(localeManager.setLocale(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
localeManager.setLocale(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will load the EmojiCompat font which has been selected.
|
||||
* If this font does not work or if the user hasn't selected one (yet), it will use a
|
||||
* fallback solution instead which won't make any visible difference to using no EmojiCompat at all.
|
||||
*/
|
||||
private void initEmojiCompat() {
|
||||
int emojiSelection = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext())
|
||||
.getInt(EmojiPreference.FONT_PREFERENCE, 0);
|
||||
EmojiCompatFont font = EmojiCompatFont.byId(emojiSelection);
|
||||
// FileEmojiCompat will handle any non-existing font and provide a fallback solution.
|
||||
EmojiCompat.Config config = font.getConfig(getApplicationContext())
|
||||
// The user probably wants to get a consistent experience
|
||||
.setReplaceAll(true);
|
||||
EmojiCompat.init(config);
|
||||
}
|
||||
|
||||
protected void initAppInjector() {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidInjector<Object> androidInjector() {
|
||||
return androidInjector;
|
||||
}
|
||||
|
||||
public interface ServiceLocator {
|
||||
<T> T get(Class<T> clazz);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.evernote.android.job.JobManager
|
||||
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 dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import org.conscrypt.Conscrypt
|
||||
import java.security.Security
|
||||
import javax.inject.Inject
|
||||
|
||||
class TuskyApplication : Application(), HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||
@Inject
|
||||
lateinit var notificationPullJobCreator: NotificationPullJobCreator
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
super.onCreate()
|
||||
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
|
||||
AutoDisposePlugins.setHideProxies(false) // a small performance optimization
|
||||
|
||||
AppInjector.init(this)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
// init the custom emoji fonts
|
||||
val emojiSelection = preferences.getInt(EmojiPreference.FONT_PREFERENCE, 0)
|
||||
val emojiConfig = EmojiCompatFont.byId(emojiSelection)
|
||||
.getConfig(this)
|
||||
.setReplaceAll(true)
|
||||
EmojiCompat.init(emojiConfig)
|
||||
|
||||
// init night mode
|
||||
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
||||
ThemeUtils.setAppNightMode(theme)
|
||||
|
||||
JobManager.create(this).addJobCreator(notificationPullJobCreator)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
localeManager = LocaleManager(base)
|
||||
super.attachBaseContext(localeManager.setLocale(base))
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
localeManager.setLocale(this)
|
||||
}
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
lateinit var localeManager: LocaleManager
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
|
@ -52,7 +53,6 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -70,6 +70,7 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
|||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
|
||||
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
|
@ -110,14 +111,12 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||
private var finishingUploadDialog: ProgressDialog? = null
|
||||
private var currentInputContentInfo: InputContentInfoCompat? = null
|
||||
private var currentFlags: Int = 0
|
||||
private var photoUploadUri: Uri? = null
|
||||
@VisibleForTesting
|
||||
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
||||
|
||||
private var composeOptions: ComposeOptions? = null
|
||||
private lateinit var viewModel: ComposeViewModel
|
||||
private val viewModel: ComposeViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private var mediaCount = 0
|
||||
|
||||
|
@ -149,11 +148,11 @@ class ComposeActivity : BaseActivity(),
|
|||
composeMediaPreviewBar.adapter = mediaAdapter
|
||||
composeMediaPreviewBar.itemAnimator = null
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java]
|
||||
|
||||
subscribeToUpdates(mediaAdapter)
|
||||
setupButtons()
|
||||
|
||||
photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY)
|
||||
|
||||
/* If the composer is started up as a reply to another post, override the "starting" state
|
||||
* based on what the intent from the reply request passes. */
|
||||
if (intent != null) {
|
||||
|
@ -544,14 +543,7 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
if (currentInputContentInfo != null) {
|
||||
outState.putParcelable("commitContentInputContentInfo",
|
||||
currentInputContentInfo!!.unwrap() as Parcelable?)
|
||||
outState.putInt("commitContentFlags", currentFlags)
|
||||
}
|
||||
currentInputContentInfo = null
|
||||
currentFlags = 0
|
||||
outState.putParcelable("photoUploadUri", photoUploadUri)
|
||||
outState.putParcelable(PHOTO_UPLOAD_URI_KEY, photoUploadUri)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
|
@ -777,47 +769,42 @@ class ComposeActivity : BaseActivity(),
|
|||
updateVisibleCharactersLeft()
|
||||
}
|
||||
|
||||
private fun verifyScheduledTime(): Boolean {
|
||||
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
|
||||
}
|
||||
|
||||
private fun onSendClicked() {
|
||||
enableButtons(false)
|
||||
sendStatus()
|
||||
if (verifyScheduledTime()) {
|
||||
sendStatus()
|
||||
} else {
|
||||
showScheduleView()
|
||||
}
|
||||
}
|
||||
|
||||
/** This is for the fancy keyboards which can insert images and stuff. */
|
||||
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle): Boolean {
|
||||
try {
|
||||
currentInputContentInfo?.releasePermission()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "InputContentInfoCompat#releasePermission() failed." + e.message)
|
||||
} finally {
|
||||
currentInputContentInfo = null
|
||||
}
|
||||
|
||||
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean {
|
||||
// Verify the returned content's type is of the correct MIME type
|
||||
val supported = inputContentInfo.description.hasMimeType("image/*")
|
||||
|
||||
return supported && onCommitContentInternal(inputContentInfo, flags)
|
||||
}
|
||||
|
||||
private fun onCommitContentInternal(inputContentInfo: InputContentInfoCompat, flags: Int): Boolean {
|
||||
if (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION != 0) {
|
||||
try {
|
||||
inputContentInfo.requestPermission()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message)
|
||||
return false
|
||||
if(supported) {
|
||||
val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
|
||||
if(lacksPermission) {
|
||||
try {
|
||||
inputContentInfo.requestPermission()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
pickMedia(inputContentInfo.contentUri, inputContentInfo)
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the file size before putting handing it off to be put in the queue.
|
||||
pickMedia(inputContentInfo.contentUri)
|
||||
|
||||
currentInputContentInfo = inputContentInfo
|
||||
currentFlags = flags
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
private fun sendStatus() {
|
||||
enableButtons(false)
|
||||
var contentText = composeEditField.text.toString()
|
||||
var spoilerText = ""
|
||||
if (viewModel.showContentWarning.value!!) {
|
||||
|
@ -927,9 +914,12 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
private fun pickMedia(uri: Uri) {
|
||||
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) {
|
||||
withLifecycleContext {
|
||||
viewModel.pickMedia(uri).observe { exceptionOrItem ->
|
||||
|
||||
contentInfoCompat?.releasePermission()
|
||||
|
||||
exceptionOrItem.asLeftOrNull()?.let {
|
||||
val errorId = when (it) {
|
||||
is VideoSizeException -> {
|
||||
|
@ -1069,7 +1059,11 @@ class ComposeActivity : BaseActivity(),
|
|||
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
||||
composeScheduleView.onTimeSet(hourOfDay, minute)
|
||||
viewModel.updateScheduledAt(composeScheduleView.time)
|
||||
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
if (verifyScheduledTime()) {
|
||||
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
} else {
|
||||
showScheduleView()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetSchedule() {
|
||||
|
@ -1107,6 +1101,7 @@ class ComposeActivity : BaseActivity(),
|
|||
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||
|
||||
private const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
||||
private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI"
|
||||
|
||||
// Mastodon only counts URLs as this long in terms of status character limits
|
||||
@VisibleForTesting
|
||||
|
|
|
@ -22,6 +22,8 @@ import android.util.AttributeSet;
|
|||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
|
@ -48,8 +50,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
|
||||
private Button resetScheduleButton;
|
||||
private TextView scheduledDateTimeView;
|
||||
private TextView invalidScheduleWarningView;
|
||||
|
||||
private Calendar scheduleDateTime;
|
||||
public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting
|
||||
|
||||
public ComposeScheduleView(Context context) {
|
||||
super(context);
|
||||
|
@ -76,8 +80,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
|
||||
resetScheduleButton = findViewById(R.id.resetScheduleButton);
|
||||
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
|
||||
invalidScheduleWarningView = findViewById(R.id.invalidScheduleWarning);
|
||||
|
||||
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
|
||||
invalidScheduleWarningView.setText(R.string.warning_scheduling_interval);
|
||||
|
||||
scheduleDateTime = null;
|
||||
|
||||
|
@ -89,10 +95,13 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
private void setScheduledDateTime() {
|
||||
if (scheduleDateTime == null) {
|
||||
scheduledDateTimeView.setText("");
|
||||
invalidScheduleWarningView.setVisibility(GONE);
|
||||
} else {
|
||||
Date scheduled = scheduleDateTime.getTime();
|
||||
scheduledDateTimeView.setText(String.format("%s %s",
|
||||
dateFormat.format(scheduleDateTime.getTime()),
|
||||
timeFormat.format(scheduleDateTime.getTime())));
|
||||
dateFormat.format(scheduled),
|
||||
timeFormat.format(scheduled)));
|
||||
verifyScheduledTime(scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,9 +133,7 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
.setValidator(
|
||||
DateValidatorPointForward.from(yesterday))
|
||||
.build();
|
||||
if (scheduleDateTime == null) {
|
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
||||
}
|
||||
initializeSuggestedTime();
|
||||
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
|
||||
.datePicker()
|
||||
.setSelection(scheduleDateTime.getTimeInMillis())
|
||||
|
@ -147,6 +154,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
|
||||
}
|
||||
|
||||
public Date getDateTime(String scheduledAt) {
|
||||
if (scheduledAt != null) {
|
||||
try {
|
||||
return iso8601.parse(scheduledAt);
|
||||
} catch (ParseException e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setDateTime(String scheduledAt) {
|
||||
Date date;
|
||||
try {
|
||||
|
@ -154,27 +171,34 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
} catch (ParseException e) {
|
||||
return;
|
||||
}
|
||||
if (scheduleDateTime == null) {
|
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
||||
}
|
||||
initializeSuggestedTime();
|
||||
scheduleDateTime.setTime(date);
|
||||
setScheduledDateTime();
|
||||
}
|
||||
|
||||
private void onDateSet(long selection) {
|
||||
if (scheduleDateTime == null) {
|
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
||||
public boolean verifyScheduledTime(@Nullable Date scheduledTime) {
|
||||
boolean valid;
|
||||
if (scheduledTime != null) {
|
||||
Calendar minimumScheduledTime = getCalendar();
|
||||
minimumScheduledTime.add(Calendar.SECOND, MINIMUM_SCHEDULED_SECONDS);
|
||||
valid = scheduledTime.after(minimumScheduledTime.getTime());
|
||||
} else {
|
||||
valid = true;
|
||||
}
|
||||
Calendar newDate = Calendar.getInstance(TimeZone.getDefault());
|
||||
invalidScheduleWarningView.setVisibility(valid ? GONE : VISIBLE);
|
||||
return valid;
|
||||
}
|
||||
|
||||
private void onDateSet(long selection) {
|
||||
initializeSuggestedTime();
|
||||
Calendar newDate = getCalendar();
|
||||
newDate.setTimeInMillis(selection);
|
||||
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
|
||||
openPickTimeDialog();
|
||||
}
|
||||
|
||||
public void onTimeSet(int hourOfDay, int minute) {
|
||||
if (scheduleDateTime == null) {
|
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
||||
}
|
||||
initializeSuggestedTime();
|
||||
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||
scheduleDateTime.set(Calendar.MINUTE, minute);
|
||||
setScheduledDateTime();
|
||||
|
@ -186,4 +210,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
|||
}
|
||||
return iso8601.format(scheduleDateTime.getTime());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Calendar getCalendar() {
|
||||
return Calendar.getInstance(TimeZone.getDefault());
|
||||
}
|
||||
|
||||
private void initializeSuggestedTime() {
|
||||
if (scheduleDateTime == null) {
|
||||
scheduleDateTime = getCalendar();
|
||||
scheduleDateTime.add(Calendar.MINUTE, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.paging.PagedList
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
@ -50,15 +50,13 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
@Inject
|
||||
lateinit var db: AppDatabase
|
||||
|
||||
private lateinit var viewModel: ConversationsViewModel
|
||||
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
||||
|
||||
private lateinit var adapter: ConversationAdapter
|
||||
|
||||
private var layoutManager: LinearLayoutManager? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[ConversationsViewModel::class.java]
|
||||
|
||||
return inflater.inflate(R.layout.fragment_timeline, container, false)
|
||||
}
|
||||
|
||||
|
@ -87,10 +85,10 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
|
||||
initSwipeToRefresh()
|
||||
|
||||
viewModel.conversations.observe(this, Observer<PagedList<ConversationEntity>> {
|
||||
viewModel.conversations.observe(viewLifecycleOwner, Observer<PagedList<ConversationEntity>> {
|
||||
adapter.submitList(it)
|
||||
})
|
||||
viewModel.networkState.observe(this, Observer {
|
||||
viewModel.networkState.observe(viewLifecycleOwner, Observer {
|
||||
adapter.setNetworkState(it)
|
||||
})
|
||||
|
||||
|
@ -99,7 +97,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
|||
}
|
||||
|
||||
private fun initSwipeToRefresh() {
|
||||
viewModel.refreshState.observe(this, Observer {
|
||||
viewModel.refreshState.observe(viewLifecycleOwner, Observer {
|
||||
swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
||||
})
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
|
|
|
@ -19,14 +19,12 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.activity_report.*
|
||||
|
@ -42,11 +40,10 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: ReportViewModel
|
||||
private val viewModel: ReportViewModel by viewModels { viewModelFactory }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[ReportViewModel::class.java]
|
||||
val accountId = intent?.getStringExtra(ACCOUNT_ID)
|
||||
val accountUserName = intent?.getStringExtra(ACCOUNT_USERNAME)
|
||||
if (accountId.isNullOrBlank() || accountUserName.isNullOrBlank()) {
|
||||
|
|
|
@ -21,8 +21,8 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
import com.keylesspalace.tusky.components.report.Screen
|
||||
|
@ -40,12 +40,7 @@ class ReportDoneFragment : Fragment(), Injectable {
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: ReportViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[ReportViewModel::class.java]
|
||||
}
|
||||
private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
|
@ -69,8 +64,8 @@ class ReportDoneFragment : Fragment(), Injectable {
|
|||
progressMute.hide()
|
||||
}
|
||||
|
||||
buttonMute.setText(when {
|
||||
it.data == true -> R.string.action_unmute
|
||||
buttonMute.setText(when (it.data) {
|
||||
true -> R.string.action_unmute
|
||||
else -> R.string.action_mute
|
||||
})
|
||||
})
|
||||
|
@ -84,8 +79,8 @@ class ReportDoneFragment : Fragment(), Injectable {
|
|||
buttonBlock.hide()
|
||||
progressBlock.hide()
|
||||
}
|
||||
buttonBlock.setText(when {
|
||||
it.data == true -> R.string.action_unblock
|
||||
buttonBlock.setText(when (it.data) {
|
||||
true -> R.string.action_unblock
|
||||
else -> R.string.action_block
|
||||
})
|
||||
})
|
||||
|
|
|
@ -21,8 +21,8 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
|
@ -39,12 +39,7 @@ class ReportNoteFragment : Fragment(), Injectable {
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: ReportViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[ReportViewModel::class.java]
|
||||
}
|
||||
private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
|
|
|
@ -22,8 +22,8 @@ import android.view.ViewGroup
|
|||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.paging.PagedList
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
@ -59,19 +59,13 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler {
|
|||
@Inject
|
||||
lateinit var accountManager: AccountManager
|
||||
|
||||
private lateinit var viewModel: ReportViewModel
|
||||
private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||
|
||||
private lateinit var adapter: StatusesAdapter
|
||||
private lateinit var layoutManager: LinearLayoutManager
|
||||
|
||||
private var snackbarErrorRetry: Snackbar? = null
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[ReportViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun showMedia(v: View?, status: Status?, idx: Int) {
|
||||
status?.actionableStatus?.let { actionable ->
|
||||
when (actionable.attachments[idx].type) {
|
||||
|
|
|
@ -21,8 +21,8 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
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
|
||||
|
@ -40,12 +40,11 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private lateinit var viewModel: SearchViewModel
|
||||
private val viewModel: SearchViewModel by viewModels { viewModelFactory }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_search)
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[SearchViewModel::class.java]
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.apply {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
|
|
|
@ -5,9 +5,9 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
@ -30,11 +30,12 @@ import javax.inject.Inject
|
|||
abstract class SearchFragment<T> : Fragment(),
|
||||
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private var snackbarErrorRetry: Snackbar? = null
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
protected lateinit var viewModel: SearchViewModel
|
||||
protected val viewModel: SearchViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||
|
||||
private var snackbarErrorRetry: Snackbar? = null
|
||||
|
||||
abstract fun createAdapter(): PagedListAdapter<T, *>
|
||||
|
||||
|
@ -43,11 +44,6 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
abstract val data: LiveData<PagedList<T>>
|
||||
protected lateinit var adapter: PagedListAdapter<T, *>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[SearchViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_search, container, false)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ package com.keylesspalace.tusky.db
|
|||
import android.util.Log
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.Comparator
|
||||
|
||||
/**
|
||||
* This class caches the account database and handles all account related operations
|
||||
|
@ -26,7 +30,8 @@ import com.keylesspalace.tusky.entity.Status
|
|||
|
||||
private const val TAG = "AccountManager"
|
||||
|
||||
class AccountManager(db: AppDatabase) {
|
||||
@Singleton
|
||||
class AccountManager @Inject constructor(db: AppDatabase) {
|
||||
|
||||
@Volatile
|
||||
var activeAccount: AccountEntity? = null
|
||||
|
@ -60,7 +65,7 @@ class AccountManager(db: AppDatabase) {
|
|||
|
||||
val maxAccountId = accounts.maxBy { it.id }?.id ?: 0
|
||||
val newAccountId = maxAccountId + 1
|
||||
activeAccount = AccountEntity(id = newAccountId, domain = domain.toLowerCase(), accessToken = accessToken, isActive = true)
|
||||
activeAccount = AccountEntity(id = newAccountId, domain = domain.toLowerCase(Locale.ROOT), accessToken = accessToken, isActive = true)
|
||||
|
||||
}
|
||||
|
||||
|
@ -146,8 +151,8 @@ class AccountManager(db: AppDatabase) {
|
|||
saveAccount(it)
|
||||
}
|
||||
|
||||
activeAccount = accounts.find { acc ->
|
||||
acc.id == accountId
|
||||
activeAccount = accounts.find { (id) ->
|
||||
id == accountId
|
||||
}
|
||||
|
||||
activeAccount?.let {
|
||||
|
@ -185,8 +190,8 @@ class AccountManager(db: AppDatabase) {
|
|||
* @return the requested account or null if it was not found
|
||||
*/
|
||||
fun getAccountById(accountId: Long): AccountEntity? {
|
||||
return accounts.find { acc ->
|
||||
acc.id == accountId
|
||||
return accounts.find { (id) ->
|
||||
id == accountId
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.room.Room
|
||||
import com.keylesspalace.tusky.TuskyApplication
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.EventHubImpl
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
|
@ -64,20 +64,23 @@ class AppModule {
|
|||
return TimelineCasesImpl(api, eventHub)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAccountManager(app: TuskyApplication): AccountManager {
|
||||
return app.serviceLocator.get(AccountManager::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesEventHub(): EventHub = EventHubImpl
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDatabase(app: TuskyApplication): AppDatabase {
|
||||
return app.serviceLocator.get(AppDatabase::class.java)
|
||||
fun providesDatabase(appContext: Context): AppDatabase {
|
||||
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
|
||||
.allowMainThreadQueries()
|
||||
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5,
|
||||
AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8,
|
||||
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
|
||||
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13,
|
||||
AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16,
|
||||
AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19,
|
||||
AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -13,14 +13,12 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
package com.keylesspalace.tusky.di
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Spanned
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.keylesspalace.tusky.BuildConfig
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||
|
@ -30,13 +28,9 @@ import com.keylesspalace.tusky.network.NotestockApi
|
|||
import com.keylesspalace.tusky.util.OkHttpUtils
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import dagger.multibindings.IntoSet
|
||||
import net.accelf.yuito.HttpToastInterceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Converter
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
@ -49,32 +43,20 @@ import javax.inject.Singleton
|
|||
@Module
|
||||
class NetworkModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ClassKey(Spanned::class)
|
||||
fun providesSpannedTypeAdapter(): JsonDeserializer<*> = SpannedTypeAdapter()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGson(adapters: @JvmSuppressWildcards Map<Class<*>, JsonDeserializer<*>>): Gson {
|
||||
fun providesGson(): Gson {
|
||||
return GsonBuilder()
|
||||
.apply {
|
||||
for ((k, v) in adapters) {
|
||||
registerTypeAdapter(k, v)
|
||||
}
|
||||
}
|
||||
.registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
|
||||
.create()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
fun providesConverterFactory(gson: Gson): Converter.Factory = GsonConverterFactory.create(gson)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesHttpClient(accountManager: AccountManager,
|
||||
context: Context): OkHttpClient {
|
||||
fun providesHttpClient(
|
||||
accountManager: AccountManager,
|
||||
context: Context
|
||||
): OkHttpClient {
|
||||
return OkHttpUtils.getCompatibleClientBuilder(context)
|
||||
.apply {
|
||||
addInterceptor(InstanceSwitchAuthInterceptor(accountManager))
|
||||
|
@ -88,18 +70,14 @@ class NetworkModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesRetrofit(httpClient: OkHttpClient,
|
||||
converters: @JvmSuppressWildcards Set<Converter.Factory>): Retrofit {
|
||||
fun providesRetrofit(
|
||||
httpClient: OkHttpClient,
|
||||
gson: Gson
|
||||
): Retrofit {
|
||||
return Retrofit.Builder().baseUrl("https://" + MastodonApi.PLACEHOLDER_DOMAIN)
|
||||
.client(httpClient)
|
||||
.let { builder ->
|
||||
// Doing it this way in case builder will be immutable so we return the final
|
||||
// instance
|
||||
converters.fold(builder) { b, c ->
|
||||
b.addConverterFactory(c)
|
||||
}
|
||||
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
||||
}
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
||||
.build()
|
||||
|
||||
}
|
||||
|
@ -111,7 +89,7 @@ class NetworkModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesNotestockApi(context: Context,
|
||||
converters: @JvmSuppressWildcards Set<Converter.Factory>): NotestockApi {
|
||||
gson: Gson): NotestockApi {
|
||||
val httpClient = OkHttpUtils.getCompatibleClientBuilder(context)
|
||||
.apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
@ -122,12 +100,8 @@ class NetworkModule {
|
|||
.build()
|
||||
val retrofit = Retrofit.Builder().baseUrl("https://notestock.osa-p.net")
|
||||
.client(httpClient)
|
||||
.let { builder ->
|
||||
converters.fold(builder) { b, c ->
|
||||
b.addConverterFactory(c)
|
||||
}
|
||||
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
||||
}
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
||||
.build()
|
||||
return retrofit.create(NotestockApi::class.java)
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
private boolean alwaysShowSensitiveMedia;
|
||||
private boolean alwaysOpenSpoiler;
|
||||
private boolean showNotificationsFilter;
|
||||
private boolean showingError;
|
||||
|
||||
// Each element is either a Notification for loading data or a Placeholder
|
||||
private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
|
||||
|
@ -280,7 +281,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
private void updateFilterVisibility() {
|
||||
CoordinatorLayout.LayoutParams params =
|
||||
(CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
|
||||
if (showNotificationsFilter) {
|
||||
if (showNotificationsFilter && !showingError && !notifications.isEmpty()) {
|
||||
appBarOptions.setExpanded(true, false);
|
||||
appBarOptions.setVisibility(View.VISIBLE);
|
||||
//Set content behaviour to hide filter on scroll
|
||||
|
@ -392,6 +393,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
@Override
|
||||
public void onRefresh() {
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
this.showingError = false;
|
||||
Either<Placeholder, Notification> first = CollectionsKt.firstOrNull(this.notifications);
|
||||
String topId;
|
||||
if (first != null && first.isRight()) {
|
||||
|
@ -674,6 +676,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
//Show friend elephant
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
|
||||
updateFilterVisibility();
|
||||
|
||||
//Update adapter
|
||||
updateAdapter();
|
||||
|
@ -999,6 +1002,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
} else {
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
}
|
||||
updateFilterVisibility();
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
@ -1014,6 +1018,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
} else if (this.notifications.isEmpty()) {
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
this.showingError = true;
|
||||
if (exception instanceof IOException) {
|
||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||
this.progressBar.setVisibility(View.VISIBLE);
|
||||
|
@ -1027,6 +1032,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
updateFilterVisibility();
|
||||
}
|
||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||
|
||||
|
|
|
@ -275,6 +275,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
|||
" - " +
|
||||
statusToShare.getContent().toString();
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, statusUrl);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to)));
|
||||
return true;
|
||||
|
|
|
@ -39,7 +39,7 @@ class BackgroundMessageView @JvmOverloads constructor(
|
|||
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
|
||||
clickListener: ((v: View) -> Unit)? = null) {
|
||||
messageTextView.setText(messageRes)
|
||||
messageTextView.setCompoundDrawablesWithIntrinsicBounds(0, imageRes, 0, 0)
|
||||
imageView.setImageResource(imageRes)
|
||||
button.setOnClickListener(clickListener)
|
||||
button.visible(clickListener != null)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -39,7 +39,9 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
tools:visibility="visible"
|
||||
app:layout_constrainedHeight="true" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/contentFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="visible"
|
||||
app:layout_constrainedHeight="true" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/topProgressBar"
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:gravity="center_horizontal"
|
||||
tools:orientation="vertical"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerInside"
|
||||
tools:src="@drawable/elephant_offline" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTextView"
|
||||
|
@ -13,7 +26,6 @@
|
|||
android:paddingRight="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
tools:drawableTop="@drawable/elephant_offline"
|
||||
tools:text="@string/error_network" />
|
||||
|
||||
<Button
|
||||
|
@ -22,5 +34,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/action_retry" />
|
||||
</merge>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/action_reset_schedule"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
|
@ -23,10 +23,27 @@
|
|||
android:paddingBottom="16dp"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toEndOf="@id/resetScheduleButton"
|
||||
tools:text="2020/01/01 00:00:00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/invalidScheduleWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="@string/warning_scheduling_interval"
|
||||
android:visibility="gone" />
|
||||
|
||||
</merge>
|
|
@ -469,5 +469,6 @@
|
|||
|
||||
<string name="no_saved_status">Du hast keine Entwürfe.</string>
|
||||
<string name="no_scheduled_status">Du hast keine geplanten Beiträge.</string>
|
||||
<string name="warning_scheduling_interval">Das Datum des geplanten Toots muss mindestens 5 Minuten in der Zukunft liegen.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -236,4 +236,27 @@
|
|||
<string name="hint_search_people_list">Nadi γef medden i teṭafareḍ</string>
|
||||
<string name="description_visiblity_private">Imeḍfaṛen</string>
|
||||
|
||||
<string name="action_links">Iseγwan</string>
|
||||
<string name="action_mentions">Tibdarin</string>
|
||||
<string name="title_mentions_dialog">Tibdarin</string>
|
||||
<string name="title_links_dialog">Iseγwan</string>
|
||||
<string name="confirmation_reported">Yettwaceyyaɛ!</string>
|
||||
<string name="status_sent">Yettwaceyyaɛ!</string>
|
||||
<string name="search_no_results">Ula d yiwen n ugmuḍ</string>
|
||||
|
||||
<string name="post_privacy_followers_only">I yimeḍfaṛen kan</string>
|
||||
|
||||
<string name="pref_status_text_size">Teγzi n weḍṛis</string>
|
||||
|
||||
<string name="about_powered_by_tusky">Yettwamdemmar s Tusky</string>
|
||||
<string name="about_project_site">Asmel Web n usenfaṛ:
|
||||
\n https://tusky.app</string>
|
||||
<string name="abbreviated_hours_ago">%dasr</string>
|
||||
<string name="abbreviated_minutes_ago">%dtas</string>
|
||||
<string name="abbreviated_seconds_ago">%dtasn</string>
|
||||
|
||||
<string name="compose_save_draft">Sekles amzun d arewway\?</string>
|
||||
<string name="later">Ticki</string>
|
||||
<string name="profile_badge_bot_text">Aṛubut</string>
|
||||
<string name="description_status_bookmarked">Yettwarna γer ticṛad</string>
|
||||
</resources>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<color name="textColorPrimary">@color/white</color>
|
||||
<color name="textColorSecondary">@color/tusky_grey_90</color>
|
||||
<color name="textColorTertiary">@color/tusky_grey_70</color>
|
||||
<color name="textColorDisabled">@color/tusky_grey_30</color>
|
||||
<color name="textColorDisabled">@color/tusky_grey_40</color>
|
||||
|
||||
<color name="iconColor">@color/tusky_grey_70</color>
|
||||
|
||||
|
|
|
@ -337,8 +337,8 @@
|
|||
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
|
||||
|
||||
<plurals name="favs">
|
||||
<item quantity="one"><b>%1$s</b> Favorit</item>
|
||||
<item quantity="other"><b>3%1$s</b>4 Favorits</item>
|
||||
<item quantity="one"><b>%1$s</b> Favorit</item>
|
||||
<item quantity="other"><b>%1$s</b> Favorits</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="reblogs">
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<string name="title_favourites">Favoritos</string>
|
||||
<string name="title_mutes">Usuários silenciados</string>
|
||||
<string name="title_blocks">Usuários bloqueados</string>
|
||||
<string name="title_follow_requests">Solicitações de seguidor</string>
|
||||
<string name="title_follow_requests">Seguidores pendentes</string>
|
||||
<string name="title_edit_profile">Editar seu perfil</string>
|
||||
<string name="title_saved_toot">Rascunhos</string>
|
||||
<string name="title_licenses">Licenças</string>
|
||||
|
@ -76,7 +76,7 @@
|
|||
<string name="action_view_favourites">Favoritos</string>
|
||||
<string name="action_view_mutes">Usuários silenciados</string>
|
||||
<string name="action_view_blocks">Usuários bloqueados</string>
|
||||
<string name="action_view_follow_requests">Solicitações de seguidor</string>
|
||||
<string name="action_view_follow_requests">Seguidores pendentes</string>
|
||||
<string name="action_view_media">Mídia</string>
|
||||
<string name="action_open_in_web">Abrir no navegador</string>
|
||||
<string name="action_add_media">Adicionar mídia</string>
|
||||
|
|
|
@ -112,4 +112,19 @@
|
|||
<string name="compose_save_draft">Uložiť koncept\?</string>
|
||||
<string name="send_toot_notification_channel_name">Odosielanie tootov</string>
|
||||
<string name="send_toot_notification_cancel_title">Odosielanie bolo zrušené</string>
|
||||
<string name="performing_lookup_title">Vyhľadávanie…</string>
|
||||
<string name="later">Neskôr</string>
|
||||
<string name="restart">Reštartovať</string>
|
||||
<string name="profile_badge_bot_text">Robot</string>
|
||||
<string name="license_cc_by_4">CC-BY 4.0</string>
|
||||
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
|
||||
|
||||
<string name="profile_metadata_label">Metadáta profilu</string>
|
||||
<string name="profile_metadata_add">pridať dáta</string>
|
||||
<string name="profile_metadata_content_label">Obsah</string>
|
||||
|
||||
<string name="conversation_1_recipients">%1$s</string>
|
||||
<string name="conversation_2_recipients">%1$s a %2$s</string>
|
||||
<string name="description_status_media_no_description_placeholder">Žiadny popis</string>
|
||||
<string name="description_visiblity_public">Verejný</string>
|
||||
</resources>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<string name="error_network">Ett nätverksfel inträffade! Kontrollera att du är ansluten till internet och försök igen! </string>
|
||||
<string name="error_empty">Det här kan inte vara tomt.</string>
|
||||
<string name="error_invalid_domain">Ogiltig domän angiven</string>
|
||||
<string name="error_failed_app_registration">Misslyckades med att autentisera med den instansen.</string>
|
||||
<string name="error_failed_app_registration">Kunde inte med att autentisera med den instansen.</string>
|
||||
<string name="error_no_web_browser_found">Det gick inte att hitta en webbläsare.</string>
|
||||
<string name="error_authorization_unknown">Ett oidentifierat behörighetsfel inträffade.</string>
|
||||
<string name="error_authorization_denied">Ingen behörighet.</string>
|
||||
|
@ -42,7 +42,7 @@
|
|||
<string name="status_boosted_format">%s knuffade</string>
|
||||
<string name="status_sensitive_media_title">Känsligt innehåll</string>
|
||||
<string name="status_media_hidden_title">Dold media</string>
|
||||
<string name="status_sensitive_media_directions">Klicka för att se</string>
|
||||
<string name="status_sensitive_media_directions">Tryck för att visa</string>
|
||||
<string name="status_content_warning_show_more">Visa mer</string>
|
||||
<string name="status_content_warning_show_less">Visa mindre</string>
|
||||
<string name="status_content_show_more">Expandera</string>
|
||||
|
@ -152,7 +152,7 @@
|
|||
<string name="dialog_message_uploading_media">Laddar upp…</string>
|
||||
<string name="dialog_download_image">Ladda ned</string>
|
||||
<string name="dialog_message_cancel_follow_request">Återkalla följningsförfrågan?</string>
|
||||
<string name="dialog_unfollow_warning">Avfölja detta konto?</string>
|
||||
<string name="dialog_unfollow_warning">Sluta följ detta konto\?</string>
|
||||
<string name="dialog_delete_toot_warning">Radera denna toot?</string>
|
||||
<string name="visibility_public">Offentlig: Skicka till offentliga tidslinjer</string>
|
||||
<string name="visibility_unlisted">Olistad: Visa inte i offentliga tidslinjer</string>
|
||||
|
@ -170,7 +170,7 @@
|
|||
<string name="pref_title_notification_filter_reblogs">mina inlägg är knuffade</string>
|
||||
<string name="pref_title_notification_filter_favourites">mina inlägg är favoriserade</string>
|
||||
<string name="pref_title_appearance_settings">Utseende</string>
|
||||
<string name="pref_title_app_theme">Applikationstema</string>
|
||||
<string name="pref_title_app_theme">Tema</string>
|
||||
<string name="pref_title_timelines">Tidslinjer</string>
|
||||
<string name="pref_title_timeline_filters">Filter</string>
|
||||
<string name="app_them_dark">Mörkt</string>
|
||||
|
@ -395,8 +395,8 @@
|
|||
<string name="notification_poll_description">Notifieringar när omröstningar har avslutats</string>
|
||||
|
||||
|
||||
<string name="poll_ended_voted">En undersökning där du har röstat är avslutad</string>
|
||||
<string name="poll_ended_created">En undersökning du skapat har avslutats</string>
|
||||
<string name="poll_ended_voted">En omröstning där du har röstat är avslutad</string>
|
||||
<string name="poll_ended_created">En omröstning som du har skapat har avslutats</string>
|
||||
|
||||
<plurals name="poll_timespan_days">
|
||||
<item quantity="one">%d dag</item>
|
||||
|
@ -419,7 +419,7 @@
|
|||
|
||||
<string name="pref_title_animate_gif_avatars">Animera profil gifar</string>
|
||||
|
||||
<string name="description_poll">Undersökning med valen: %1$s, %2$s, %3$s, %4$s; %5$s</string>
|
||||
<string name="description_poll">Omröstning med valen: %1$s, %2$s, %3$s, %4$s; %5$s</string>
|
||||
|
||||
<string name="title_domain_mutes">Dolda domäner</string>
|
||||
<string name="action_view_domain_mutes">Dolda domäner</string>
|
||||
|
@ -438,7 +438,7 @@
|
|||
<string name="report_remote_instance">Vidarebefordra till %s</string>
|
||||
<string name="failed_report">Misslyckades att anmäla</string>
|
||||
<string name="failed_fetch_statuses">Misslyckades att hämta status</string>
|
||||
<string name="report_description_1">Anmälan kommer att skickas till din serveradminstratör. Du kan beskriva varför du anmäler kontot nedan:</string>
|
||||
<string name="report_description_1">Anmälan kommer att skickas till din servermoderator. Du kan beskriva varför du anmäler kontot nedan:</string>
|
||||
<string name="report_description_remote_instance">Kontot är från en annan server. Skicka en anonym kopia av anmälan dit också\?</string>
|
||||
|
||||
<string name="pref_title_show_notifications_filter">Visa notifikationsfilter</string>
|
||||
|
@ -448,7 +448,7 @@
|
|||
<string name="title_accounts">Konton</string>
|
||||
<string name="failed_search">Sökning misslyckades</string>
|
||||
|
||||
<string name="action_add_poll">Lägg till omröstning</string>
|
||||
<string name="action_add_poll">Skapa en omröstning</string>
|
||||
<string name="create_poll_title">Omröstning</string>
|
||||
<string name="poll_duration_5_min">5 minuter</string>
|
||||
<string name="poll_duration_30_min">30 minuter</string>
|
||||
|
@ -463,7 +463,7 @@
|
|||
<string name="edit_poll">Redigera</string>
|
||||
|
||||
<string name="title_scheduled_toot">Schemalagda toots</string>
|
||||
<string name="action_edit">Redigera</string>
|
||||
<string name="action_edit">Ändra</string>
|
||||
<string name="action_access_scheduled_toot">Schemalagda toots</string>
|
||||
<string name="action_schedule_toot">Schemalägg toot</string>
|
||||
<string name="action_reset_schedule">Återställ</string>
|
||||
|
|
|
@ -575,5 +575,6 @@
|
|||
|
||||
<string name="no_saved_status">You don\'t have any drafts.</string>
|
||||
<string name="no_scheduled_status">You don\'t have any scheduled statuses.</string>
|
||||
<string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -119,10 +119,21 @@ class BottomSheetActivityTest {
|
|||
arrayOf("https://mastodon.foo.bar/@user/345667890345678", true),
|
||||
arrayOf("https://mastodon.foo.bar/@user/3", true),
|
||||
arrayOf("https://pleroma.foo.bar/users/meh3223", true),
|
||||
arrayOf("https://pleroma.foo.bar/users/meh3223_bruh", true),
|
||||
arrayOf("https://pleroma.foo.bar/users/2345", true),
|
||||
arrayOf("https://pleroma.foo.bar/notice/9", true),
|
||||
arrayOf("https://pleroma.foo.bar/notice/9345678", true),
|
||||
arrayOf("https://pleroma.foo.bar/notice/wat", true),
|
||||
arrayOf("https://pleroma.foo.bar/notice/9qTHT2ANWUdXzENqC0", true),
|
||||
arrayOf("https://pleroma.foo.bar/objects/abcdef-123-abcd-9876543", true),
|
||||
arrayOf("https://misskey.foo.bar/notes/mew", true),
|
||||
arrayOf("https://misskey.foo.bar/notes/1421564653", true),
|
||||
arrayOf("https://misskey.foo.bar/notes/qwer615985ddf", true),
|
||||
arrayOf("https://friendica.foo.bar/profile/user", true),
|
||||
arrayOf("https://friendica.foo.bar/profile/uSeR", true),
|
||||
arrayOf("https://friendica.foo.bar/profile/user_user", true),
|
||||
arrayOf("https://friendica.foo.bar/profile/123", true),
|
||||
arrayOf("https://friendica.foo.bar/display/abcdef-123-abcd-9876543", true),
|
||||
arrayOf("https://google.com/", false),
|
||||
arrayOf("https://mastodon.foo.bar/@User?foo=bar", false),
|
||||
arrayOf("https://mastodon.foo.bar/@User#foo", false),
|
||||
|
@ -131,13 +142,23 @@ class BottomSheetActivityTest {
|
|||
arrayOf("https://mastodon.foo.bar/@user/345667890345678/", false),
|
||||
arrayOf("https://mastodon.foo.bar/@user/3abce", false),
|
||||
arrayOf("https://pleroma.foo.bar/users/", false),
|
||||
arrayOf("https://pleroma.foo.bar/users/meow/", false),
|
||||
arrayOf("https://pleroma.foo.bar/users/@meow", false),
|
||||
arrayOf("https://pleroma.foo.bar/user/2345", false),
|
||||
arrayOf("https://pleroma.foo.bar/notice/wat", false),
|
||||
arrayOf("https://pleroma.foo.bar/notices/123456", false),
|
||||
arrayOf("https://pleroma.foo.bar/notice/@neverhappen/", false),
|
||||
arrayOf("https://pleroma.foo.bar/object/abcdef-123-abcd-9876543", false),
|
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd-9876543", false),
|
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd-9876543/", false),
|
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd_9876543", false)
|
||||
arrayOf("https://pleroma.foo.bar/objects/xabcdef-123-abcd_9876543", false),
|
||||
arrayOf("https://friendica.foo.bar/display/xabcdef-123-abcd-9876543", false),
|
||||
arrayOf("https://friendica.foo.bar/display/xabcdef-123-abcd-9876543/", false),
|
||||
arrayOf("https://friendica.foo.bar/display/xabcdef-123-abcd_9876543", false),
|
||||
arrayOf("https://friendica.foo.bar/profile/@mew", false),
|
||||
arrayOf("https://friendica.foo.bar/profile/@mew/", false),
|
||||
arrayOf("https://misskey.foo.bar/notes/@nyan", false),
|
||||
arrayOf("https://misskey.foo.bar/notes/NYAN123", false),
|
||||
arrayOf("https://misskey.foo.bar/notes/meow123/", false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -299,4 +320,4 @@ class BottomSheetActivityTest {
|
|||
this.fallbackBehavior = fallbackBehavior
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ import org.robolectric.fakes.RoboMenuItem
|
|||
* Created by charlag on 3/7/18.
|
||||
*/
|
||||
|
||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ComposeActivityTest {
|
||||
private lateinit var activity: ComposeActivity
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package com.keylesspalace.tusky
|
||||
|
||||
/**
|
||||
* Created by charlag on 3/7/18.
|
||||
*/
|
||||
|
||||
class FakeTuskyApplication : TuskyApplication() {
|
||||
|
||||
private lateinit var locator: ServiceLocator
|
||||
|
||||
override fun initSecurityProvider() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
override fun initAppInjector() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
override fun initNightMode() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
override fun getServiceLocator(): ServiceLocator {
|
||||
return locator
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ import retrofit2.Callback
|
|||
import retrofit2.Response
|
||||
import java.util.*
|
||||
|
||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FilterTest {
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.util.Log
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import com.keylesspalace.tusky.util.LocaleManager
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import de.c1710.filemojicompat.FileEmojiCompatConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
// override TuskyApplication for Robolectric tests, only initialize the necessary stuff
|
||||
class TuskyApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
EmojiCompat.init(FileEmojiCompatConfig(this, ""))
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
localeManager = LocaleManager(base)
|
||||
super.attachBaseContext(localeManager.setLocale(base))
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
localeManager.setLocale(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
lateinit var localeManager: LocaleManager
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
package com.keylesspalace.tusky.di
|
||||
|
|
@ -2,14 +2,13 @@ package com.keylesspalace.tusky.util
|
|||
|
||||
import android.text.SpannableStringBuilder
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.keylesspalace.tusky.FakeTuskyApplication
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SmartLengthInputFilterTest {
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Tusky v10.0
|
||||
|
||||
- Du kan nå legge til statuser som bokmerker, og se bokmerkene i Tusky.
|
||||
- Du kan nå planlegge et toot for publisering i framtiden.
|
||||
- Du kan nå legge til lister på hovedskjermen.
|
||||
- Du kan nå publisere lydvedlegg med Tusky.
|
||||
|
||||
I tillegg er det mange andre mindre forbedringer og feilrettinger!
|
|
@ -0,0 +1,8 @@
|
|||
Tusky v10.0
|
||||
|
||||
- Pra quem não aguenta mais perder toots no meio dos favoritos, o Salvos chegou!
|
||||
- Agora dá para agendar toots, porém é necessário agendá-los para ao menos 5 minutos depois, certo?
|
||||
- Utilidade pública: Finalmente poderemos adicionar listas na barrinha do Tusky!
|
||||
- Filosofou no áudio de uma conversa e quer compartilhar com o fediverso? Você já pode anexar áudios nos toots, só não se esqueça de descrevê-los!
|
||||
|
||||
E muitas outras pequenas melhorias e correções de bugs!
|
|
@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx4096m
|
|||
# use parallel execution
|
||||
org.gradle.parallel=true
|
||||
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
android.enableUnitTestBinaryResources=true
|
||||
android.enableR8.fullMode=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
|
Loading…
Reference in New Issue