Merge upstream
|
@ -104,20 +104,20 @@ project.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.lifecycleVersion = "2.1.0"
|
ext.lifecycleVersion = "2.2.0"
|
||||||
ext.roomVersion = '2.2.3'
|
ext.roomVersion = '2.2.4'
|
||||||
ext.retrofitVersion = '2.6.0'
|
ext.retrofitVersion = '2.7.1'
|
||||||
ext.okhttpVersion = '4.3.1'
|
ext.okhttpVersion = '4.3.1'
|
||||||
ext.glideVersion = '4.10.0'
|
ext.glideVersion = '4.10.0'
|
||||||
ext.daggerVersion = '2.25.3'
|
ext.daggerVersion = '2.26'
|
||||||
|
|
||||||
// if libraries are changed here, they should also be changed in LicenseActivity
|
// if libraries are changed here, they should also be changed in LicenseActivity
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
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.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.browser:browser:1.2.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
|
@ -127,8 +127,9 @@ dependencies {
|
||||||
implementation "androidx.sharetarget:sharetarget:1.0.0-rc01"
|
implementation "androidx.sharetarget:sharetarget:1.0.0-rc01"
|
||||||
implementation "androidx.emoji:emoji:1.0.0"
|
implementation "androidx.emoji:emoji:1.0.0"
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.1"
|
||||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||||
|
@ -136,8 +137,8 @@ dependencies {
|
||||||
implementation "androidx.room:room-rxjava2:$roomVersion"
|
implementation "androidx.room:room-rxjava2:$roomVersion"
|
||||||
kapt "androidx.room:room-compiler:$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.google.android:flexbox:2.0.1'
|
implementation 'com.google.android:flexbox:2.0.1'
|
||||||
|
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||||
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
|
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.Px
|
import androidx.annotation.Px
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
@ -34,7 +35,6 @@ import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.emoji.text.EmojiCompat
|
import androidx.emoji.text.EmojiCompat
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.viewpager2.widget.MarginPageTransformer
|
import androidx.viewpager2.widget.MarginPageTransformer
|
||||||
|
@ -76,7 +76,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: AccountViewModel
|
private val viewModel: AccountViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
private val accountFieldAdapter = AccountFieldAdapter(this)
|
private val accountFieldAdapter = AccountFieldAdapter(this)
|
||||||
|
|
||||||
|
@ -117,9 +117,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
loadResources()
|
loadResources()
|
||||||
makeNotificationBarTransparent()
|
makeNotificationBarTransparent()
|
||||||
setContentView(R.layout.activity_account)
|
setContentView(R.layout.activity_account)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[AccountViewModel::class.java]
|
|
||||||
|
|
||||||
// Obtain information to fill out the profile.
|
// Obtain information to fill out the profile.
|
||||||
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
|
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!)
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(TuskyApplication.localeManager.setLocale(base));
|
super.attachBaseContext(TuskyApplication.getLocaleManager().setLocale(base));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean requiresLogin() {
|
protected boolean requiresLogin() {
|
||||||
|
|
|
@ -19,7 +19,6 @@ import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
@ -34,6 +33,7 @@ import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.activity.viewModels
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
@ -69,7 +69,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: EditProfileViewModel
|
private val viewModel: EditProfileViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
private var currentlyPicking: PickType = PickType.NOTHING
|
private var currentlyPicking: PickType = PickType.NOTHING
|
||||||
|
|
||||||
|
@ -90,8 +90,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
setContentView(R.layout.activity_edit_profile)
|
setContentView(R.layout.activity_edit_profile)
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[EditProfileViewModel::class.java]
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.run {
|
supportActionBar?.run {
|
||||||
setTitle(R.string.title_edit_profile)
|
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, AppDatabase.MIGRATION_21_22)
|
|
||||||
.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
|
||||||
|
}
|
||||||
|
}
|
|
@ -399,18 +399,21 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
return ImageLoadingHelper.decodeBlurHash(this.avatar.getContext(), blurhash);
|
return ImageLoadingHelper.decodeBlurHash(this.avatar.getContext(), blurhash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadImage(MediaPreviewImageView imageView, String previewUrl, MetaData meta,
|
private void loadImage(MediaPreviewImageView imageView,
|
||||||
|
@Nullable String previewUrl,
|
||||||
|
@Nullable MetaData meta,
|
||||||
@Nullable String blurhash) {
|
@Nullable String blurhash) {
|
||||||
|
|
||||||
Drawable placeholder = blurhash != null ? decodeBlurHash(blurhash) : mediaPreviewUnloaded;
|
Drawable placeholder = blurhash != null ? decodeBlurHash(blurhash) : mediaPreviewUnloaded;
|
||||||
|
|
||||||
if (TextUtils.isEmpty(previewUrl)) {
|
if (TextUtils.isEmpty(previewUrl)) {
|
||||||
if (blurhash != null) {
|
imageView.removeFocalPoint();
|
||||||
imageView.setImageDrawable(decodeBlurHash(blurhash));
|
|
||||||
} else {
|
Glide.with(imageView)
|
||||||
Glide.with(imageView)
|
.load(placeholder)
|
||||||
.load(placeholder)
|
.centerInside()
|
||||||
.centerInside()
|
.into(imageView);
|
||||||
.into(imageView);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Focus focus = meta != null ? meta.getFocus() : null;
|
Focus focus = meta != null ? meta.getFocus() : null;
|
||||||
|
|
||||||
|
@ -469,18 +472,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
imageView.setContentDescription(description);
|
imageView.setContentDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showingContent) {
|
loadImage(
|
||||||
loadImage(imageView, previewUrl, attachment.getMeta(), attachment.getBlurhash());
|
imageView,
|
||||||
} else {
|
showingContent ? previewUrl : null,
|
||||||
imageView.setFocalPoint(null);
|
attachment.getMeta(),
|
||||||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
useBlurhash ? attachment.getBlurhash() : null
|
||||||
if (useBlurhash && attachment.getBlurhash() != null) {
|
);
|
||||||
BitmapDrawable blurhashBitmap = decodeBlurHash(attachment.getBlurhash());
|
|
||||||
imageView.setImageDrawable(blurhashBitmap);
|
|
||||||
} else {
|
|
||||||
imageView.setImageDrawable(mediaPreviewUnloaded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Attachment.Type type = attachment.getType();
|
final Attachment.Type type = attachment.getType();
|
||||||
if (showingContent && (type == Attachment.Type.VIDEO || type == Attachment.Type.GIFV)) {
|
if (showingContent && (type == Attachment.Type.VIDEO || type == Attachment.Type.GIFV)) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
@ -51,7 +52,6 @@ import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -67,6 +67,7 @@ import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||||
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
|
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.db.AccountEntity
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
@ -106,14 +107,12 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||||
private var finishingUploadDialog: ProgressDialog? = null
|
private var finishingUploadDialog: ProgressDialog? = null
|
||||||
private var currentInputContentInfo: InputContentInfoCompat? = null
|
|
||||||
private var currentFlags: Int = 0
|
|
||||||
private var photoUploadUri: Uri? = null
|
private var photoUploadUri: Uri? = null
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
||||||
|
|
||||||
private var composeOptions: ComposeOptions? = null
|
private var composeOptions: ComposeOptions? = null
|
||||||
private lateinit var viewModel: ComposeViewModel
|
private val viewModel: ComposeViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
private var mediaCount = 0
|
private var mediaCount = 0
|
||||||
|
|
||||||
|
@ -145,11 +144,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
composeMediaPreviewBar.adapter = mediaAdapter
|
composeMediaPreviewBar.adapter = mediaAdapter
|
||||||
composeMediaPreviewBar.itemAnimator = null
|
composeMediaPreviewBar.itemAnimator = null
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[ComposeViewModel::class.java]
|
|
||||||
|
|
||||||
subscribeToUpdates(mediaAdapter)
|
subscribeToUpdates(mediaAdapter)
|
||||||
setupButtons()
|
setupButtons()
|
||||||
|
|
||||||
|
photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY)
|
||||||
|
|
||||||
/* If the composer is started up as a reply to another post, override the "starting" state
|
/* If the composer is started up as a reply to another post, override the "starting" state
|
||||||
* based on what the intent from the reply request passes. */
|
* based on what the intent from the reply request passes. */
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
|
@ -241,15 +240,14 @@ class ComposeActivity : BaseActivity(),
|
||||||
if (action != null && action == Intent.ACTION_SEND) {
|
if (action != null && action == Intent.ACTION_SEND) {
|
||||||
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
val shareBody = if (subject != null && text != null) {
|
val shareBody = text ?: subject
|
||||||
if (subject != text && !text.contains(subject)) {
|
|
||||||
String.format("%s\n%s", subject, text)
|
|
||||||
} else {
|
|
||||||
text
|
|
||||||
}
|
|
||||||
} else text ?: subject
|
|
||||||
|
|
||||||
if (shareBody != null) {
|
if (shareBody != null) {
|
||||||
|
if (!subject.isNullOrBlank() && subject !in shareBody) {
|
||||||
|
composeContentWarningField.setText(subject)
|
||||||
|
viewModel.showContentWarning.value = true
|
||||||
|
}
|
||||||
|
|
||||||
val start = composeEditField.selectionStart.coerceAtLeast(0)
|
val start = composeEditField.selectionStart.coerceAtLeast(0)
|
||||||
val end = composeEditField.selectionEnd.coerceAtLeast(0)
|
val end = composeEditField.selectionEnd.coerceAtLeast(0)
|
||||||
val left = min(start, end)
|
val left = min(start, end)
|
||||||
|
@ -465,10 +463,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
||||||
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
||||||
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
||||||
val textToInsert = if (
|
val textToInsert = if (start > 0 && !composeEditField.text[start - 1].isWhitespace()) {
|
||||||
composeEditField.text.isNotEmpty()
|
" $text"
|
||||||
&& !composeEditField.text[start - 1].isWhitespace()
|
} else {
|
||||||
) " $text" else text
|
text
|
||||||
|
}
|
||||||
composeEditField.text.replace(start, end, textToInsert)
|
composeEditField.text.replace(start, end, textToInsert)
|
||||||
|
|
||||||
// Set the cursor after the inserted text
|
// Set the cursor after the inserted text
|
||||||
|
@ -569,15 +568,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
if (currentInputContentInfo != null) {
|
outState.putParcelable(PHOTO_UPLOAD_URI_KEY, photoUploadUri)
|
||||||
outState.putParcelable("commitContentInputContentInfo",
|
|
||||||
currentInputContentInfo!!.unwrap() as Parcelable?)
|
|
||||||
outState.putInt("commitContentFlags", currentFlags)
|
|
||||||
}
|
|
||||||
currentInputContentInfo = null
|
|
||||||
currentFlags = 0
|
|
||||||
outState.putParcelable("photoUploadUri", photoUploadUri)
|
|
||||||
outState.putBoolean("markdownMode", viewModel.markdownMode)
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,47 +791,42 @@ class ComposeActivity : BaseActivity(),
|
||||||
updateVisibleCharactersLeft()
|
updateVisibleCharactersLeft()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun verifyScheduledTime(): Boolean {
|
||||||
|
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
|
||||||
|
}
|
||||||
|
|
||||||
private fun onSendClicked() {
|
private fun onSendClicked() {
|
||||||
enableButtons(false)
|
if (verifyScheduledTime()) {
|
||||||
sendStatus()
|
sendStatus()
|
||||||
|
} else {
|
||||||
|
showScheduleView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is for the fancy keyboards which can insert images and stuff. */
|
/** This is for the fancy keyboards which can insert images and stuff. */
|
||||||
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle): Boolean {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the returned content's type is of the correct MIME type
|
// Verify the returned content's type is of the correct MIME type
|
||||||
val supported = inputContentInfo.description.hasMimeType("image/*")
|
val supported = inputContentInfo.description.hasMimeType("image/*")
|
||||||
|
|
||||||
return supported && onCommitContentInternal(inputContentInfo, flags)
|
if(supported) {
|
||||||
}
|
val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
|
||||||
|
if(lacksPermission) {
|
||||||
private fun onCommitContentInternal(inputContentInfo: InputContentInfoCompat, flags: Int): Boolean {
|
try {
|
||||||
if (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION != 0) {
|
inputContentInfo.requestPermission()
|
||||||
try {
|
} catch (e: Exception) {
|
||||||
inputContentInfo.requestPermission()
|
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message)
|
||||||
} catch (e: Exception) {
|
return false
|
||||||
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.
|
return false
|
||||||
pickMedia(inputContentInfo.contentUri)
|
|
||||||
|
|
||||||
currentInputContentInfo = inputContentInfo
|
|
||||||
currentFlags = flags
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendStatus() {
|
private fun sendStatus() {
|
||||||
|
enableButtons(false)
|
||||||
val contentText = composeEditField.text.toString()
|
val contentText = composeEditField.text.toString()
|
||||||
var spoilerText = ""
|
var spoilerText = ""
|
||||||
if (viewModel.showContentWarning.value!!) {
|
if (viewModel.showContentWarning.value!!) {
|
||||||
|
@ -857,7 +843,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
viewModel.sendStatus(contentText, spoilerText).observe(this, Observer {
|
viewModel.sendStatus(contentText, spoilerText).observe(this, Observer {
|
||||||
finishingUploadDialog?.dismiss()
|
finishingUploadDialog?.dismiss()
|
||||||
finishWithoutSlideOutAnimation()
|
deleteDraftAndFinish()
|
||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -949,9 +935,12 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pickMedia(uri: Uri) {
|
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) {
|
||||||
withLifecycleContext {
|
withLifecycleContext {
|
||||||
viewModel.pickMedia(uri, uriToFilename(uri)).observe { exceptionOrItem ->
|
viewModel.pickMedia(uri, uriToFilename(uri)).observe { exceptionOrItem ->
|
||||||
|
|
||||||
|
contentInfoCompat?.releasePermission()
|
||||||
|
|
||||||
exceptionOrItem.asLeftOrNull()?.let {
|
exceptionOrItem.asLeftOrNull()?.let {
|
||||||
val errorId = when (it) {
|
val errorId = when (it) {
|
||||||
is VideoSizeException -> {
|
is VideoSizeException -> {
|
||||||
|
@ -1099,7 +1088,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
||||||
composeScheduleView.onTimeSet(hourOfDay, minute)
|
composeScheduleView.onTimeSet(hourOfDay, minute)
|
||||||
viewModel.updateScheduledAt(composeScheduleView.time)
|
viewModel.updateScheduledAt(composeScheduleView.time)
|
||||||
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
if (verifyScheduledTime()) {
|
||||||
|
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
} else {
|
||||||
|
showScheduleView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetSchedule() {
|
private fun resetSchedule() {
|
||||||
|
@ -1135,6 +1128,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||||
|
|
||||||
private const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
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
|
// Mastodon only counts URLs as this long in terms of status character limits
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.util.AttributeSet;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
|
@ -48,8 +50,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
|
|
||||||
private Button resetScheduleButton;
|
private Button resetScheduleButton;
|
||||||
private TextView scheduledDateTimeView;
|
private TextView scheduledDateTimeView;
|
||||||
|
private TextView invalidScheduleWarningView;
|
||||||
|
|
||||||
private Calendar scheduleDateTime;
|
private Calendar scheduleDateTime;
|
||||||
|
public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting
|
||||||
|
|
||||||
public ComposeScheduleView(Context context) {
|
public ComposeScheduleView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -76,8 +80,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
|
|
||||||
resetScheduleButton = findViewById(R.id.resetScheduleButton);
|
resetScheduleButton = findViewById(R.id.resetScheduleButton);
|
||||||
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
|
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
|
||||||
|
invalidScheduleWarningView = findViewById(R.id.invalidScheduleWarning);
|
||||||
|
|
||||||
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
|
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
|
||||||
|
invalidScheduleWarningView.setText(R.string.warning_scheduling_interval);
|
||||||
|
|
||||||
scheduleDateTime = null;
|
scheduleDateTime = null;
|
||||||
|
|
||||||
|
@ -89,10 +95,13 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
private void setScheduledDateTime() {
|
private void setScheduledDateTime() {
|
||||||
if (scheduleDateTime == null) {
|
if (scheduleDateTime == null) {
|
||||||
scheduledDateTimeView.setText("");
|
scheduledDateTimeView.setText("");
|
||||||
|
invalidScheduleWarningView.setVisibility(GONE);
|
||||||
} else {
|
} else {
|
||||||
|
Date scheduled = scheduleDateTime.getTime();
|
||||||
scheduledDateTimeView.setText(String.format("%s %s",
|
scheduledDateTimeView.setText(String.format("%s %s",
|
||||||
dateFormat.format(scheduleDateTime.getTime()),
|
dateFormat.format(scheduled),
|
||||||
timeFormat.format(scheduleDateTime.getTime())));
|
timeFormat.format(scheduled)));
|
||||||
|
verifyScheduledTime(scheduled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +133,7 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
.setValidator(
|
.setValidator(
|
||||||
DateValidatorPointForward.from(yesterday))
|
DateValidatorPointForward.from(yesterday))
|
||||||
.build();
|
.build();
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
|
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
|
||||||
.datePicker()
|
.datePicker()
|
||||||
.setSelection(scheduleDateTime.getTimeInMillis())
|
.setSelection(scheduleDateTime.getTimeInMillis())
|
||||||
|
@ -147,6 +154,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
|
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) {
|
public void setDateTime(String scheduledAt) {
|
||||||
Date date;
|
Date date;
|
||||||
try {
|
try {
|
||||||
|
@ -154,27 +171,34 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
scheduleDateTime.setTime(date);
|
scheduleDateTime.setTime(date);
|
||||||
setScheduledDateTime();
|
setScheduledDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDateSet(long selection) {
|
public boolean verifyScheduledTime(@Nullable Date scheduledTime) {
|
||||||
if (scheduleDateTime == null) {
|
boolean valid;
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
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);
|
newDate.setTimeInMillis(selection);
|
||||||
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
|
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
|
||||||
openPickTimeDialog();
|
openPickTimeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTimeSet(int hourOfDay, int minute) {
|
public void onTimeSet(int hourOfDay, int minute) {
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||||
scheduleDateTime.set(Calendar.MINUTE, minute);
|
scheduleDateTime.set(Calendar.MINUTE, minute);
|
||||||
setScheduledDateTime();
|
setScheduledDateTime();
|
||||||
|
@ -186,4 +210,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
return iso8601.format(scheduleDateTime.getTime());
|
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.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -50,15 +50,13 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var db: AppDatabase
|
lateinit var db: AppDatabase
|
||||||
|
|
||||||
private lateinit var viewModel: ConversationsViewModel
|
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
private lateinit var adapter: ConversationAdapter
|
private lateinit var adapter: ConversationAdapter
|
||||||
|
|
||||||
private var layoutManager: LinearLayoutManager? = null
|
private var layoutManager: LinearLayoutManager? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
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)
|
return inflater.inflate(R.layout.fragment_timeline, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +85,10 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
|
|
||||||
initSwipeToRefresh()
|
initSwipeToRefresh()
|
||||||
|
|
||||||
viewModel.conversations.observe(this, Observer<PagedList<ConversationEntity>> {
|
viewModel.conversations.observe(viewLifecycleOwner, Observer<PagedList<ConversationEntity>> {
|
||||||
adapter.submitList(it)
|
adapter.submitList(it)
|
||||||
})
|
})
|
||||||
viewModel.networkState.observe(this, Observer {
|
viewModel.networkState.observe(viewLifecycleOwner, Observer {
|
||||||
adapter.setNetworkState(it)
|
adapter.setNetworkState(it)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -99,7 +97,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initSwipeToRefresh() {
|
private fun initSwipeToRefresh() {
|
||||||
viewModel.refreshState.observe(this, Observer {
|
viewModel.refreshState.observe(viewLifecycleOwner, Observer {
|
||||||
swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
swipeRefreshLayout.isRefreshing = it == NetworkState.LOADING
|
||||||
})
|
})
|
||||||
swipeRefreshLayout.setOnRefreshListener {
|
swipeRefreshLayout.setOnRefreshListener {
|
||||||
|
|
|
@ -19,14 +19,12 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.activity.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.keylesspalace.tusky.BottomSheetActivity
|
import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
|
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils
|
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import kotlinx.android.synthetic.main.activity_report.*
|
import kotlinx.android.synthetic.main.activity_report.*
|
||||||
|
@ -42,11 +40,10 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: ReportViewModel
|
private val viewModel: ReportViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[ReportViewModel::class.java]
|
|
||||||
val accountId = intent?.getStringExtra(ACCOUNT_ID)
|
val accountId = intent?.getStringExtra(ACCOUNT_ID)
|
||||||
val accountUserName = intent?.getStringExtra(ACCOUNT_USERNAME)
|
val accountUserName = intent?.getStringExtra(ACCOUNT_USERNAME)
|
||||||
if (accountId.isNullOrBlank() || accountUserName.isNullOrBlank()) {
|
if (accountId.isNullOrBlank() || accountUserName.isNullOrBlank()) {
|
||||||
|
|
|
@ -21,8 +21,8 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||||
import com.keylesspalace.tusky.components.report.Screen
|
import com.keylesspalace.tusky.components.report.Screen
|
||||||
|
@ -40,12 +40,7 @@ class ReportDoneFragment : Fragment(), Injectable {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: ReportViewModel
|
private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[ReportViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
@ -69,8 +64,8 @@ class ReportDoneFragment : Fragment(), Injectable {
|
||||||
progressMute.hide()
|
progressMute.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonMute.setText(when {
|
buttonMute.setText(when (it.data) {
|
||||||
it.data == true -> R.string.action_unmute
|
true -> R.string.action_unmute
|
||||||
else -> R.string.action_mute
|
else -> R.string.action_mute
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -84,8 +79,8 @@ class ReportDoneFragment : Fragment(), Injectable {
|
||||||
buttonBlock.hide()
|
buttonBlock.hide()
|
||||||
progressBlock.hide()
|
progressBlock.hide()
|
||||||
}
|
}
|
||||||
buttonBlock.setText(when {
|
buttonBlock.setText(when (it.data) {
|
||||||
it.data == true -> R.string.action_unblock
|
true -> R.string.action_unblock
|
||||||
else -> R.string.action_block
|
else -> R.string.action_block
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,8 +21,8 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||||
|
@ -39,12 +39,7 @@ class ReportNoteFragment : Fragment(), Injectable {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: ReportViewModel
|
private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)[ReportViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
|
|
|
@ -22,8 +22,8 @@ import android.view.ViewGroup
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -59,19 +59,13 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var accountManager: AccountManager
|
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 adapter: StatusesAdapter
|
||||||
private lateinit var layoutManager: LinearLayoutManager
|
private lateinit var layoutManager: LinearLayoutManager
|
||||||
|
|
||||||
private var snackbarErrorRetry: Snackbar? = null
|
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) {
|
override fun showMedia(v: View?, status: Status?, idx: Int) {
|
||||||
status?.actionableStatus?.let { actionable ->
|
status?.actionableStatus?.let { actionable ->
|
||||||
when (actionable.attachments[idx].type) {
|
when (actionable.attachments[idx].type) {
|
||||||
|
|
|
@ -21,8 +21,8 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.keylesspalace.tusky.BottomSheetActivity
|
import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
|
@ -40,12 +40,11 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
private lateinit var viewModel: SearchViewModel
|
private val viewModel: SearchViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_search)
|
setContentView(R.layout.activity_search)
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[SearchViewModel::class.java]
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.apply {
|
supportActionBar?.apply {
|
||||||
setDisplayHomeAsUpEnabled(true)
|
setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
|
@ -5,9 +5,9 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import androidx.paging.PagedListAdapter
|
import androidx.paging.PagedListAdapter
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
@ -30,11 +30,12 @@ import javax.inject.Inject
|
||||||
abstract class SearchFragment<T> : Fragment(),
|
abstract class SearchFragment<T> : Fragment(),
|
||||||
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
|
||||||
private var snackbarErrorRetry: Snackbar? = null
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
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, *>
|
abstract fun createAdapter(): PagedListAdapter<T, *>
|
||||||
|
|
||||||
|
@ -43,11 +44,6 @@ abstract class SearchFragment<T> : Fragment(),
|
||||||
abstract val data: LiveData<PagedList<T>>
|
abstract val data: LiveData<PagedList<T>>
|
||||||
protected lateinit var adapter: PagedListAdapter<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? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.fragment_search, container, false)
|
return inflater.inflate(R.layout.fragment_search, container, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,10 @@ package com.keylesspalace.tusky.db
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.entity.Status
|
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
|
* 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"
|
private const val TAG = "AccountManager"
|
||||||
|
|
||||||
class AccountManager(db: AppDatabase) {
|
@Singleton
|
||||||
|
class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var activeAccount: AccountEntity? = null
|
var activeAccount: AccountEntity? = null
|
||||||
|
@ -60,7 +65,7 @@ class AccountManager(db: AppDatabase) {
|
||||||
|
|
||||||
val maxAccountId = accounts.maxBy { it.id }?.id ?: 0
|
val maxAccountId = accounts.maxBy { it.id }?.id ?: 0
|
||||||
val newAccountId = maxAccountId + 1
|
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)
|
saveAccount(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
activeAccount = accounts.find { acc ->
|
activeAccount = accounts.find { (id) ->
|
||||||
acc.id == accountId
|
id == accountId
|
||||||
}
|
}
|
||||||
|
|
||||||
activeAccount?.let {
|
activeAccount?.let {
|
||||||
|
@ -185,8 +190,8 @@ class AccountManager(db: AppDatabase) {
|
||||||
* @return the requested account or null if it was not found
|
* @return the requested account or null if it was not found
|
||||||
*/
|
*/
|
||||||
fun getAccountById(accountId: Long): AccountEntity? {
|
fun getAccountById(accountId: Long): AccountEntity? {
|
||||||
return accounts.find { acc ->
|
return accounts.find { (id) ->
|
||||||
acc.id == accountId
|
id == accountId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,10 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.room.Room
|
||||||
import com.keylesspalace.tusky.TuskyApplication
|
import com.keylesspalace.tusky.TuskyApplication
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.EventHubImpl
|
import com.keylesspalace.tusky.appstore.EventHubImpl
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
|
||||||
import com.keylesspalace.tusky.db.AppDatabase
|
import com.keylesspalace.tusky.db.AppDatabase
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.network.TimelineCases
|
import com.keylesspalace.tusky.network.TimelineCases
|
||||||
|
@ -64,20 +64,23 @@ class AppModule {
|
||||||
return TimelineCasesImpl(api, eventHub)
|
return TimelineCasesImpl(api, eventHub)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providesAccountManager(app: TuskyApplication): AccountManager {
|
|
||||||
return app.serviceLocator.get(AccountManager::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesEventHub(): EventHub = EventHubImpl
|
fun providesEventHub(): EventHub = EventHubImpl
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesDatabase(app: TuskyApplication): AppDatabase {
|
fun providesDatabase(appContext: Context): AppDatabase {
|
||||||
return app.serviceLocator.get(AppDatabase::class.java)
|
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, AppDatabase.MIGRATION_21_22)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -85,4 +88,4 @@ class AppModule {
|
||||||
fun providesHtmlConverter(): HtmlConverter {
|
fun providesHtmlConverter(): HtmlConverter {
|
||||||
return HtmlConverterImpl()
|
return HtmlConverterImpl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,12 @@
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
* see <http://www.gnu.org/licenses>. */
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonDeserializer
|
|
||||||
import com.keylesspalace.tusky.BuildConfig
|
import com.keylesspalace.tusky.BuildConfig
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||||
|
@ -29,12 +27,8 @@ import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.OkHttpUtils
|
import com.keylesspalace.tusky.util.OkHttpUtils
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.ClassKey
|
|
||||||
import dagger.multibindings.IntoMap
|
|
||||||
import dagger.multibindings.IntoSet
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Converter
|
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
@ -47,32 +41,20 @@ import javax.inject.Singleton
|
||||||
@Module
|
@Module
|
||||||
class NetworkModule {
|
class NetworkModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ClassKey(Spanned::class)
|
|
||||||
fun providesSpannedTypeAdapter(): JsonDeserializer<*> = SpannedTypeAdapter()
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesGson(adapters: @JvmSuppressWildcards Map<Class<*>, JsonDeserializer<*>>): Gson {
|
fun providesGson(): Gson {
|
||||||
return GsonBuilder()
|
return GsonBuilder()
|
||||||
.apply {
|
.registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
|
||||||
for ((k, v) in adapters) {
|
|
||||||
registerTypeAdapter(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoSet
|
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesConverterFactory(gson: Gson): Converter.Factory = GsonConverterFactory.create(gson)
|
fun providesHttpClient(
|
||||||
|
accountManager: AccountManager,
|
||||||
@Provides
|
context: Context
|
||||||
@Singleton
|
): OkHttpClient {
|
||||||
fun providesHttpClient(accountManager: AccountManager,
|
|
||||||
context: Context): OkHttpClient {
|
|
||||||
return OkHttpUtils.getCompatibleClientBuilder(context)
|
return OkHttpUtils.getCompatibleClientBuilder(context)
|
||||||
.apply {
|
.apply {
|
||||||
addInterceptor(InstanceSwitchAuthInterceptor(accountManager))
|
addInterceptor(InstanceSwitchAuthInterceptor(accountManager))
|
||||||
|
@ -85,18 +67,14 @@ class NetworkModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesRetrofit(httpClient: OkHttpClient,
|
fun providesRetrofit(
|
||||||
converters: @JvmSuppressWildcards Set<Converter.Factory>): Retrofit {
|
httpClient: OkHttpClient,
|
||||||
|
gson: Gson
|
||||||
|
): Retrofit {
|
||||||
return Retrofit.Builder().baseUrl("https://" + MastodonApi.PLACEHOLDER_DOMAIN)
|
return Retrofit.Builder().baseUrl("https://" + MastodonApi.PLACEHOLDER_DOMAIN)
|
||||||
.client(httpClient)
|
.client(httpClient)
|
||||||
.let { builder ->
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
// Doing it this way in case builder will be immutable so we return the final
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
||||||
// instance
|
|
||||||
converters.fold(builder) { b, c ->
|
|
||||||
b.addConverterFactory(c)
|
|
||||||
}
|
|
||||||
builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
|
|
||||||
}
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
private boolean alwaysShowSensitiveMedia;
|
private boolean alwaysShowSensitiveMedia;
|
||||||
private boolean alwaysOpenSpoiler;
|
private boolean alwaysOpenSpoiler;
|
||||||
private boolean showNotificationsFilter;
|
private boolean showNotificationsFilter;
|
||||||
|
private boolean showingError;
|
||||||
|
|
||||||
// Each element is either a Notification for loading data or a Placeholder
|
// Each element is either a Notification for loading data or a Placeholder
|
||||||
private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
|
private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
|
||||||
|
@ -275,7 +276,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
private void updateFilterVisibility() {
|
private void updateFilterVisibility() {
|
||||||
CoordinatorLayout.LayoutParams params =
|
CoordinatorLayout.LayoutParams params =
|
||||||
(CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
|
(CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
|
||||||
if (showNotificationsFilter) {
|
if (showNotificationsFilter && !showingError && !notifications.isEmpty()) {
|
||||||
appBarOptions.setExpanded(true, false);
|
appBarOptions.setExpanded(true, false);
|
||||||
appBarOptions.setVisibility(View.VISIBLE);
|
appBarOptions.setVisibility(View.VISIBLE);
|
||||||
//Set content behaviour to hide filter on scroll
|
//Set content behaviour to hide filter on scroll
|
||||||
|
@ -413,6 +414,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh() {
|
public void onRefresh() {
|
||||||
this.statusView.setVisibility(View.GONE);
|
this.statusView.setVisibility(View.GONE);
|
||||||
|
this.showingError = false;
|
||||||
Either<Placeholder, Notification> first = CollectionsKt.firstOrNull(this.notifications);
|
Either<Placeholder, Notification> first = CollectionsKt.firstOrNull(this.notifications);
|
||||||
String topId;
|
String topId;
|
||||||
if (first != null && first.isRight()) {
|
if (first != null && first.isRight()) {
|
||||||
|
@ -722,6 +724,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
//Show friend elephant
|
//Show friend elephant
|
||||||
this.statusView.setVisibility(View.VISIBLE);
|
this.statusView.setVisibility(View.VISIBLE);
|
||||||
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
|
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
|
||||||
|
updateFilterVisibility();
|
||||||
|
|
||||||
//Update adapter
|
//Update adapter
|
||||||
updateAdapter();
|
updateAdapter();
|
||||||
|
@ -1049,6 +1052,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
} else {
|
} else {
|
||||||
swipeRefreshLayout.setEnabled(true);
|
swipeRefreshLayout.setEnabled(true);
|
||||||
}
|
}
|
||||||
|
updateFilterVisibility();
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -1064,6 +1068,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
} else if (this.notifications.isEmpty()) {
|
} else if (this.notifications.isEmpty()) {
|
||||||
this.statusView.setVisibility(View.VISIBLE);
|
this.statusView.setVisibility(View.VISIBLE);
|
||||||
swipeRefreshLayout.setEnabled(false);
|
swipeRefreshLayout.setEnabled(false);
|
||||||
|
this.showingError = true;
|
||||||
if (exception instanceof IOException) {
|
if (exception instanceof IOException) {
|
||||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||||
this.progressBar.setVisibility(View.VISIBLE);
|
this.progressBar.setVisibility(View.VISIBLE);
|
||||||
|
@ -1077,6 +1082,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateFilterVisibility();
|
||||||
}
|
}
|
||||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
||||||
" - " +
|
" - " +
|
||||||
statusToShare.getContent().toString();
|
statusToShare.getContent().toString();
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
|
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, statusUrl);
|
||||||
sendIntent.setType("text/plain");
|
sendIntent.setType("text/plain");
|
||||||
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to)));
|
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to)));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
|
|
||||||
|
@ -144,7 +145,7 @@ public class EmojiCompatFont {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable getThumb(Context context) {
|
public Drawable getThumb(Context context) {
|
||||||
return context.getResources().getDrawable(img);
|
return ContextCompat.getDrawable(context, img);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
|
|
|
@ -39,7 +39,7 @@ class BackgroundMessageView @JvmOverloads constructor(
|
||||||
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
|
fun setup(@DrawableRes imageRes: Int, @StringRes messageRes: Int,
|
||||||
clickListener: ((v: View) -> Unit)? = null) {
|
clickListener: ((v: View) -> Unit)? = null) {
|
||||||
messageTextView.setText(messageRes)
|
messageTextView.setText(messageRes)
|
||||||
messageTextView.setCompoundDrawablesWithIntrinsicBounds(0, imageRes, 0, 0)
|
imageView.setImageResource(imageRes)
|
||||||
button.setOnClickListener(clickListener)
|
button.setOnClickListener(clickListener)
|
||||||
button.visible(clickListener != null)
|
button.visible(clickListener != null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<size android:width="4dp" />
|
<size android:width="4dp" />
|
||||||
<solid android:color="?attr/colorBackgroundAccent" />
|
<solid android:color="?attr/dividerColor" />
|
||||||
</shape>
|
</shape>
|
|
@ -4,6 +4,6 @@
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:viewportWidth="24">
|
android:viewportWidth="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?android:attr/textColorSecondary"
|
android:fillColor="?attr/iconColor"
|
||||||
android:pathData="M12,17.5C14.33,17.5 16.3,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5M8.5,11A1.5,1.5 0 0,0 10,9.5A1.5,1.5 0 0,0 8.5,8A1.5,1.5 0 0,0 7,9.5A1.5,1.5 0 0,0 8.5,11M15.5,11A1.5,1.5 0 0,0 17,9.5A1.5,1.5 0 0,0 15.5,8A1.5,1.5 0 0,0 14,9.5A1.5,1.5 0 0,0 15.5,11M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
android:pathData="M12,17.5C14.33,17.5 16.3,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5M8.5,11A1.5,1.5 0 0,0 10,9.5A1.5,1.5 0 0,0 8.5,8A1.5,1.5 0 0,0 7,9.5A1.5,1.5 0 0,0 8.5,11M15.5,11A1.5,1.5 0 0,0 17,9.5A1.5,1.5 0 0,0 15.5,8A1.5,1.5 0 0,0 14,9.5A1.5,1.5 0 0,0 15.5,11M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||||
</vector>
|
</vector>
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -39,7 +39,9 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||||
|
tools:visibility="visible"
|
||||||
|
app:layout_constrainedHeight="true" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/contentFrame"
|
android:id="@+id/contentFrame"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<include layout="@layout/toolbar_basic" />
|
<include layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible"
|
||||||
|
app:layout_constrainedHeight="true" />
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
android:id="@+id/topProgressBar"
|
android:id="@+id/topProgressBar"
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshLayout"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
<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
|
<TextView
|
||||||
android:id="@+id/messageTextView"
|
android:id="@+id/messageTextView"
|
||||||
|
@ -13,7 +26,6 @@
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
tools:drawableTop="@drawable/elephant_offline"
|
|
||||||
tools:text="@string/error_network" />
|
tools:text="@string/error_network" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -22,5 +34,6 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
android:text="@string/action_retry" />
|
android:text="@string/action_retry" />
|
||||||
</merge>
|
</merge>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:text="@string/action_reset_schedule"
|
android:text="@string/action_reset_schedule"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -23,10 +23,27 @@
|
||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:textColor="?android:textColorTertiary"
|
android:textColor="?android:textColorTertiary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintStart_toEndOf="@id/resetScheduleButton"
|
app:layout_constraintStart_toEndOf="@id/resetScheduleButton"
|
||||||
tools:text="2020/01/01 00:00:00" />
|
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>
|
</merge>
|
|
@ -469,5 +469,6 @@
|
||||||
|
|
||||||
<string name="no_saved_status">Du hast keine Entwürfe.</string>
|
<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="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>
|
</resources>
|
||||||
|
|
|
@ -236,4 +236,27 @@
|
||||||
<string name="hint_search_people_list">Nadi γef medden i teṭafareḍ</string>
|
<string name="hint_search_people_list">Nadi γef medden i teṭafareḍ</string>
|
||||||
<string name="description_visiblity_private">Imeḍfaṛen</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>
|
</resources>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<color name="textColorPrimary">@color/white</color>
|
<color name="textColorPrimary">@color/white</color>
|
||||||
<color name="textColorSecondary">@color/tusky_grey_90</color>
|
<color name="textColorSecondary">@color/tusky_grey_90</color>
|
||||||
<color name="textColorTertiary">@color/tusky_grey_70</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>
|
<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>
|
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
|
||||||
|
|
||||||
<plurals name="favs">
|
<plurals name="favs">
|
||||||
<item quantity="one"><b>%1$s</b> Favorit</item>
|
<item quantity="one"><b>%1$s</b> Favorit</item>
|
||||||
<item quantity="other"><b>3%1$s</b>4 Favorits</item>
|
<item quantity="other"><b>%1$s</b> Favorits</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<plurals name="reblogs">
|
<plurals name="reblogs">
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<string name="title_favourites">Favoritos</string>
|
<string name="title_favourites">Favoritos</string>
|
||||||
<string name="title_mutes">Usuários silenciados</string>
|
<string name="title_mutes">Usuários silenciados</string>
|
||||||
<string name="title_blocks">Usuários bloqueados</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_edit_profile">Editar seu perfil</string>
|
||||||
<string name="title_saved_toot">Rascunhos</string>
|
<string name="title_saved_toot">Rascunhos</string>
|
||||||
<string name="title_licenses">Licenças</string>
|
<string name="title_licenses">Licenças</string>
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
<string name="action_view_favourites">Favoritos</string>
|
<string name="action_view_favourites">Favoritos</string>
|
||||||
<string name="action_view_mutes">Usuários silenciados</string>
|
<string name="action_view_mutes">Usuários silenciados</string>
|
||||||
<string name="action_view_blocks">Usuários bloqueados</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_view_media">Mídia</string>
|
||||||
<string name="action_open_in_web">Abrir no navegador</string>
|
<string name="action_open_in_web">Abrir no navegador</string>
|
||||||
<string name="action_add_media">Adicionar mídia</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="compose_save_draft">Uložiť koncept\?</string>
|
||||||
<string name="send_toot_notification_channel_name">Odosielanie tootov</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="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>
|
</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_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_empty">Det här kan inte vara tomt.</string>
|
||||||
<string name="error_invalid_domain">Ogiltig domän angiven</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_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_unknown">Ett oidentifierat behörighetsfel inträffade.</string>
|
||||||
<string name="error_authorization_denied">Ingen behörighet.</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_boosted_format">%s knuffade</string>
|
||||||
<string name="status_sensitive_media_title">Känsligt innehåll</string>
|
<string name="status_sensitive_media_title">Känsligt innehåll</string>
|
||||||
<string name="status_media_hidden_title">Dold media</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_more">Visa mer</string>
|
||||||
<string name="status_content_warning_show_less">Visa mindre</string>
|
<string name="status_content_warning_show_less">Visa mindre</string>
|
||||||
<string name="status_content_show_more">Expandera</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_message_uploading_media">Laddar upp…</string>
|
||||||
<string name="dialog_download_image">Ladda ned</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_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="dialog_delete_toot_warning">Radera denna toot?</string>
|
||||||
<string name="visibility_public">Offentlig: Skicka till offentliga tidslinjer</string>
|
<string name="visibility_public">Offentlig: Skicka till offentliga tidslinjer</string>
|
||||||
<string name="visibility_unlisted">Olistad: Visa inte i 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_reblogs">mina inlägg är knuffade</string>
|
||||||
<string name="pref_title_notification_filter_favourites">mina inlägg är favoriserade</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_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_timelines">Tidslinjer</string>
|
||||||
<string name="pref_title_timeline_filters">Filter</string>
|
<string name="pref_title_timeline_filters">Filter</string>
|
||||||
<string name="app_them_dark">Mörkt</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="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_voted">En omröstning 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_created">En omröstning som du har skapat har avslutats</string>
|
||||||
|
|
||||||
<plurals name="poll_timespan_days">
|
<plurals name="poll_timespan_days">
|
||||||
<item quantity="one">%d dag</item>
|
<item quantity="one">%d dag</item>
|
||||||
|
@ -419,7 +419,7 @@
|
||||||
|
|
||||||
<string name="pref_title_animate_gif_avatars">Animera profil gifar</string>
|
<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="title_domain_mutes">Dolda domäner</string>
|
||||||
<string name="action_view_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="report_remote_instance">Vidarebefordra till %s</string>
|
||||||
<string name="failed_report">Misslyckades att anmäla</string>
|
<string name="failed_report">Misslyckades att anmäla</string>
|
||||||
<string name="failed_fetch_statuses">Misslyckades att hämta status</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="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>
|
<string name="pref_title_show_notifications_filter">Visa notifikationsfilter</string>
|
||||||
|
@ -448,7 +448,7 @@
|
||||||
<string name="title_accounts">Konton</string>
|
<string name="title_accounts">Konton</string>
|
||||||
<string name="failed_search">Sökning misslyckades</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="create_poll_title">Omröstning</string>
|
||||||
<string name="poll_duration_5_min">5 minuter</string>
|
<string name="poll_duration_5_min">5 minuter</string>
|
||||||
<string name="poll_duration_30_min">30 minuter</string>
|
<string name="poll_duration_30_min">30 minuter</string>
|
||||||
|
@ -463,7 +463,7 @@
|
||||||
<string name="edit_poll">Redigera</string>
|
<string name="edit_poll">Redigera</string>
|
||||||
|
|
||||||
<string name="title_scheduled_toot">Schemalagda toots</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_access_scheduled_toot">Schemalagda toots</string>
|
||||||
<string name="action_schedule_toot">Schemalägg toot</string>
|
<string name="action_schedule_toot">Schemalägg toot</string>
|
||||||
<string name="action_reset_schedule">Återställ</string>
|
<string name="action_reset_schedule">Återställ</string>
|
||||||
|
|
|
@ -547,5 +547,6 @@
|
||||||
|
|
||||||
<string name="no_saved_status">You don\'t have any drafts.</string>
|
<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="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>
|
</resources>
|
||||||
|
|
|
@ -46,7 +46,7 @@ import org.robolectric.fakes.RoboMenuItem
|
||||||
* Created by charlag on 3/7/18.
|
* Created by charlag on 3/7/18.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
@Config(sdk = [28])
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ComposeActivityTest {
|
class ComposeActivityTest {
|
||||||
private lateinit var activity: ComposeActivity
|
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 retrofit2.Response
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
@Config(sdk = [28])
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class FilterTest {
|
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 android.text.SpannableStringBuilder
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.keylesspalace.tusky.FakeTuskyApplication
|
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@Config(application = FakeTuskyApplication::class, sdk = [28])
|
@Config(sdk = [28])
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SmartLengthInputFilterTest {
|
class SmartLengthInputFilterTest {
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 976 KiB After Width: | Height: | Size: 998 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 817 KiB After Width: | Height: | Size: 625 KiB |
Before Width: | Height: | Size: 926 KiB After Width: | Height: | Size: 964 KiB |
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 317 KiB |
|
@ -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,5 @@ org.gradle.jvmargs=-Xmx4096m
|
||||||
# use parallel execution
|
# use parallel execution
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|
||||||
android.enableJetifier=true
|
|
||||||
android.useAndroidX=true
|
|
||||||
android.enableUnitTestBinaryResources=true
|
android.enableUnitTestBinaryResources=true
|
||||||
android.enableR8.fullMode=true
|
android.enableR8.fullMode=true
|
||||||
|
|