From 8ce4ca4159c4535c7a2b2ce839b1ba9f1788487c Mon Sep 17 00:00:00 2001 From: HuanChengFly <609486518@qq.com> Date: Thu, 17 Jun 2021 16:56:51 +0800 Subject: [PATCH] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E5=A4=9C=E9=97=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=87=AA=E5=8A=A8=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tieba/post/activities/BaseActivity.kt | 12 +- .../tieba/post/activities/MainActivity.kt | 36 +--- .../tieba/post/activities/ThemeActivity.kt | 166 ++++++++---------- .../tieba/post/adapters/ThemeAdapter.java | 6 + .../tieba/post/fragments/MyInfoFragment.kt | 29 ++- .../tieba/post/utils/AppPreferencesUtils.kt | 4 + .../tieba/post/utils/ThemeUtil.java | 35 +--- app/src/main/res/values/strings.xml | 3 + 8 files changed, 137 insertions(+), 154 deletions(-) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/BaseActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/BaseActivity.kt index 2097d172..7b683c20 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/BaseActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/BaseActivity.kt @@ -51,8 +51,7 @@ abstract class BaseActivity : SwipeBackActivity(), ExtraRefreshable, CoroutineSc private var customStatusColor = -1 private var statusBarTinted = false - val appPreferences: AppPreferencesUtils - get() = AppPreferencesUtils(this) + val appPreferences: AppPreferencesUtils by lazy { AppPreferencesUtils(this) } override fun onPause() { super.onPause() @@ -112,6 +111,15 @@ abstract class BaseActivity : SwipeBackActivity(), ExtraRefreshable, CoroutineSc override fun onResume() { super.onResume() isActivityRunning = true + if (appPreferences.followSystemNight) { + if (BaseApplication.isSystemNight && !ThemeUtil.isNightMode(this)) { + SharedPreferencesUtil.put(ThemeUtil.getSharedPreferences(this), MainActivity.SP_SHOULD_SHOW_SNACKBAR, true) + ThemeUtil.switchToNightMode(this, false) + } else if (!BaseApplication.isSystemNight && ThemeUtil.isNightMode(this)) { + SharedPreferencesUtil.put(ThemeUtil.getSharedPreferences(this), MainActivity.SP_SHOULD_SHOW_SNACKBAR, true) + ThemeUtil.switchFromNightMode(this, false) + } + } refreshUIIfNeed() } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/MainActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/MainActivity.kt index 6e0f16a3..96509688 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/MainActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/MainActivity.kt @@ -26,8 +26,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationMenuView import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView.OnNavigationItemReselectedListener import com.google.android.material.snackbar.Snackbar -import com.huanchengfly.tieba.post.BaseApplication -import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.* import com.huanchengfly.tieba.post.adapters.ViewPagerAdapter import com.huanchengfly.tieba.post.api.Error import com.huanchengfly.tieba.post.api.LiteApi.Companion.instance @@ -39,15 +38,13 @@ import com.huanchengfly.tieba.post.fragments.MainForumListFragment import com.huanchengfly.tieba.post.fragments.MessageFragment import com.huanchengfly.tieba.post.fragments.MyInfoFragment import com.huanchengfly.tieba.post.fragments.PersonalizedFeedFragment -import com.huanchengfly.tieba.post.getColorCompat -import com.huanchengfly.tieba.post.goToActivity import com.huanchengfly.tieba.post.interfaces.Refreshable import com.huanchengfly.tieba.post.models.MyInfoBean import com.huanchengfly.tieba.post.services.NotifyJobService import com.huanchengfly.tieba.post.utils.* import com.huanchengfly.tieba.post.widgets.MyViewPager -class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, OnNavigationItemReselectedListener { +open class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, OnNavigationItemReselectedListener { var mAdapter: ViewPagerAdapter = ViewPagerAdapter(supportFragmentManager) @BindView(R.id.mViewPager) @@ -70,23 +67,12 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelect get() = if (hideExplore) 1 else 2 public override fun onResume() { - val reason = ThemeUtil.getSharedPreferences(this).getString(ThemeUtil.SP_SWITCH_REASON, null) - val followSystemNight = appPreferences.followSystemNight - if (followSystemNight) { - if (BaseApplication.isSystemNight && !ThemeUtil.isNightMode(this)) { - SharedPreferencesUtil.put(ThemeUtil.getSharedPreferences(this), SP_SHOULD_SHOW_SNACKBAR, true) - ThemeUtil.switchToNightMode(this, ThemeUtil.REASON_FOLLOW_SYSTEM, false) - } else if (!BaseApplication.isSystemNight && ThemeUtil.isNightMode(this) && TextUtils.equals(reason, ThemeUtil.REASON_FOLLOW_SYSTEM)) { - SharedPreferencesUtil.put(ThemeUtil.getSharedPreferences(this), SP_SHOULD_SHOW_SNACKBAR, true) - ThemeUtil.switchFromNightMode(this, ThemeUtil.REASON_FOLLOW_SYSTEM, false) - } - } super.onResume() ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)) - if (ThemeUtil.THEME_TRANSLUCENT == ThemeUtil.getTheme(this)) { - mBottomNavigationView.elevation = 0f + mBottomNavigationView.elevation = if (ThemeUtil.THEME_TRANSLUCENT == ThemeUtil.getTheme(this)) { + 0f } else { - mBottomNavigationView.elevation = DisplayUtil.dp2px(this, 4f).toFloat() + 4f.dpToPxFloat() } } @@ -162,14 +148,7 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelect }) } - @SuppressLint("ApplySharedPref") - protected fun clearSwitchReason() { - if (TextUtils.equals(ThemeUtil.getSharedPreferences(this).getString(ThemeUtil.SP_SWITCH_REASON, null), ThemeUtil.REASON_MANUALLY)) { - ThemeUtil.getSharedPreferences(this).edit().remove(ThemeUtil.SP_SWITCH_REASON).commit() - } - } - - protected fun shouldShowSwitchSnackbar(): Boolean { + private fun shouldShowSwitchSnackbar(): Boolean { return ThemeUtil.getSharedPreferences(this).getBoolean(SP_SHOULD_SHOW_SNACKBAR, false) } @@ -192,9 +171,6 @@ class MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelect .setCancelable(false) .create()) } - if (savedInstanceState == null) { - clearSwitchReason() - } if (shouldShowSwitchSnackbar()) { Util.createSnackbar(mViewPager, if (ThemeUtil.isNightMode(this)) R.string.snackbar_auto_switch_to_night else R.string.snackbar_auto_switch_from_night, Snackbar.LENGTH_SHORT) .show() diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/ThemeActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/ThemeActivity.kt index aab258b7..fd667de2 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/ThemeActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/ThemeActivity.kt @@ -1,100 +1,88 @@ -package com.huanchengfly.tieba.post.activities; +package com.huanchengfly.tieba.post.activities -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.Bundle; +import android.os.Bundle +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.SimpleItemAnimator +import butterknife.BindView +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.adapters.ThemeAdapter +import com.huanchengfly.tieba.post.goToActivity +import com.huanchengfly.tieba.post.interfaces.OnItemClickListener +import com.huanchengfly.tieba.post.utils.DialogUtil +import com.huanchengfly.tieba.post.utils.SharedPreferencesUtil +import com.huanchengfly.tieba.post.utils.ThemeUtil -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.SimpleItemAnimator; +class ThemeActivity : BaseActivity() { + @BindView(R.id.theme_list) + lateinit var mRecyclerView: RecyclerView -import com.huanchengfly.tieba.post.R; -import com.huanchengfly.tieba.post.adapters.ThemeAdapter; -import com.huanchengfly.tieba.post.utils.SharedPreferencesUtil; -import com.huanchengfly.tieba.post.utils.ThemeUtil; + override fun getLayoutId(): Int { + return R.layout.activity_theme + } -import java.util.Arrays; -import java.util.List; - -import static com.huanchengfly.tieba.post.utils.ThemeUtil.SP_TRANSLUCENT_THEME_BACKGROUND_PATH; -import static com.huanchengfly.tieba.post.utils.ThemeUtil.THEME_TRANSLUCENT; - -public class ThemeActivity extends BaseActivity { - public static final String TAG = "ThemeActivity"; - - private long lastClickTimestamp = 0; - private int clickTimes = 0; - - private RecyclerView mRecyclerView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_theme); - mRecyclerView = (RecyclerView) findViewById(R.id.theme_list); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - ThemeAdapter themeAdapter = new ThemeAdapter(this); - mRecyclerView.setAdapter(themeAdapter); - if (mRecyclerView.getItemAnimator() != null) { - mRecyclerView.getItemAnimator().setAddDuration(0); - mRecyclerView.getItemAnimator().setChangeDuration(0); - mRecyclerView.getItemAnimator().setMoveDuration(0); - mRecyclerView.getItemAnimator().setRemoveDuration(0); - ((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mRecyclerView.layoutManager = LinearLayoutManager(this) + val themeAdapter = ThemeAdapter(this) + mRecyclerView.adapter = themeAdapter + if (mRecyclerView.itemAnimator != null) { + mRecyclerView.itemAnimator!!.addDuration = 0 + mRecyclerView.itemAnimator!!.changeDuration = 0 + mRecyclerView.itemAnimator!!.moveDuration = 0 + mRecyclerView.itemAnimator!!.removeDuration = 0 + (mRecyclerView.itemAnimator as SimpleItemAnimator?)!!.supportsChangeAnimations = false } - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - /* - toolbar.setOnClickListener(v -> { - if (System.currentTimeMillis() - lastClickTimestamp < 2000) { - clickTimes += 1; - } else { - clickTimes = 0; - } - if (clickTimes >= 7) { - clickTimes = 0; - startActivity(new Intent(this, TranslucentThemeActivity.class)); - Toast.makeText(this, "\uD83D\uDC23", Toast.LENGTH_SHORT).show(); - finish(); - } else if (clickTimes >= 2) { - Toast.makeText(this, "\uD83E\uDD5A", Toast.LENGTH_SHORT).show(); - } - lastClickTimestamp = System.currentTimeMillis(); - }); - */ - setSupportActionBar(toolbar); - ActionBar actionBar = getSupportActionBar(); + val toolbar = findViewById(R.id.toolbar) as Toolbar + setSupportActionBar(toolbar) + val actionBar = supportActionBar if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(R.string.title_theme); + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.setTitle(R.string.title_theme) } - List values = Arrays.asList(getResources().getStringArray(R.array.theme_values)); - themeAdapter.setOnItemClickListener((itemView, str, position, viewType) -> { - String backgroundFilePath = SharedPreferencesUtil.get(this, SharedPreferencesUtil.SP_SETTINGS) - .getString(SP_TRANSLUCENT_THEME_BACKGROUND_PATH, null); - if (values.get(position).equals(THEME_TRANSLUCENT) && backgroundFilePath == null) { - startActivity(new Intent(this, TranslucentThemeActivity.class)); + val values = listOf(*resources.getStringArray(R.array.theme_values)) + themeAdapter.onItemClickListener = OnItemClickListener { _, _, position: Int, _ -> + val backgroundFilePath = SharedPreferencesUtil.get(this, SharedPreferencesUtil.SP_SETTINGS) + .getString(ThemeUtil.SP_TRANSLUCENT_THEME_BACKGROUND_PATH, null) + val theme = values[position] + if (theme == ThemeUtil.THEME_TRANSLUCENT && backgroundFilePath == null) { + goToActivity() + } + if (ThemeUtil.isNightMode(theme) != ThemeUtil.isNightMode(appPreferences.theme)) { + DialogUtil.build(this) + .setMessage(R.string.message_dialog_follow_system_night) + .setPositiveButton(R.string.btn_keep_following) { _, _ -> + themeAdapter.refresh() + } + .setNegativeButton(R.string.btn_close_following) { _, _ -> + appPreferences.followSystemNight = false + setTheme(theme) + } + .show() + } else { + setTheme(theme) } - setTheme(values.get(position)); - }); - mRecyclerView.setAdapter(themeAdapter); - ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)); - } - - @Override - protected void onResume() { - super.onResume(); - ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)); - } - - @SuppressLint("ApplySharedPref") - private void setTheme(String theme) { - ThemeUtil.getSharedPreferences(ThemeActivity.this).edit().putString(ThemeUtil.SP_THEME, theme).commit(); - if (!theme.contains("dark")) { - ThemeUtil.getSharedPreferences(ThemeActivity.this).edit().putString(ThemeUtil.SP_OLD_THEME, theme).commit(); } - refreshUIIfNeed(); - ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)); + mRecyclerView.adapter = themeAdapter + ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)) + } + + override fun onResume() { + super.onResume() + ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)) + } + + private fun setTheme(theme: String) { + appPreferences.theme = theme + if (!theme.contains("dark")) { + appPreferences.oldTheme = theme + } + refreshUIIfNeed() + ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background)) + } + + companion object { + const val TAG = "ThemeActivity" } } \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/adapters/ThemeAdapter.java b/app/src/main/java/com/huanchengfly/tieba/post/adapters/ThemeAdapter.java index baac2f27..74219620 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/adapters/ThemeAdapter.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/adapters/ThemeAdapter.java @@ -48,6 +48,12 @@ public class ThemeAdapter extends RecyclerView.Adapter implements selectedPosition = themeList.indexOf(ThemeUtil.getTheme(mContext)); } + public void refresh() { + List themeList = Arrays.asList(themes); + selectedPosition = themeList.indexOf(ThemeUtil.getTheme(mContext)); + notifyDataSetChanged(); + } + public OnItemClickListener getOnItemClickListener() { return onItemClickListener; } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/fragments/MyInfoFragment.kt b/app/src/main/java/com/huanchengfly/tieba/post/fragments/MyInfoFragment.kt index 423adce7..7f1607ab 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/fragments/MyInfoFragment.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/fragments/MyInfoFragment.kt @@ -207,9 +207,7 @@ class MyInfoFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCh override fun onResume() { super.onResume() - nightSwitch.setOnCheckedChangeListener(null) - nightSwitch.isChecked = ThemeUtil.isNightMode(attachContext) - nightSwitch.setOnCheckedChangeListener(this) + refreshNightModeStatus() listOf( R.id.my_info_history, R.id.my_info_service_center, @@ -249,13 +247,36 @@ class MyInfoFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCh } override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { - if (isChecked) { + if (appPreferences.followSystemNight) { + DialogUtil.build(attachContext) + .setMessage(R.string.message_dialog_follow_system_night) + .setPositiveButton(R.string.btn_keep_following) { _, _ -> + refreshNightModeStatus() + } + .setNegativeButton(R.string.btn_close_following) { _, _ -> + attachContext.appPreferences.followSystemNight = false + switchNightMode(isChecked) + } + .show() + } else { + switchNightMode(isChecked) + } + } + + fun switchNightMode(isNightMode: Boolean) { + if (isNightMode) { ThemeUtil.switchToNightMode(attachContext as Activity) } else { ThemeUtil.switchFromNightMode(attachContext as Activity) } } + private fun refreshNightModeStatus() { + nightSwitch.setOnCheckedChangeListener(null) + nightSwitch.isChecked = ThemeUtil.isNightMode(attachContext) + nightSwitch.setOnCheckedChangeListener(this) + } + override fun onRefresh() { if (isFragmentVisible) { refresh(true) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/AppPreferencesUtils.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/AppPreferencesUtils.kt index 06aed6e7..32b9c9fa 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/AppPreferencesUtils.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/AppPreferencesUtils.kt @@ -48,6 +48,8 @@ open class AppPreferencesUtils(context: Context) { var loadPictureWhenScroll by SharedPreferenceDelegates.boolean(defaultValue = true) + var oldTheme by SharedPreferenceDelegates.string(key = "old_theme") + var oksignSlowMode by SharedPreferenceDelegates.boolean(defaultValue = true, key = "oksign_slow_mode") var radius by SharedPreferenceDelegates.int(defaultValue = 8) @@ -62,6 +64,8 @@ open class AppPreferencesUtils(context: Context) { var statusBarDarker by SharedPreferenceDelegates.boolean(defaultValue = true, key = "status_bar_darker") + var theme by SharedPreferenceDelegates.string(defaultValue = ThemeUtil.THEME_WHITE) + var translucentBackgroundAlpha by SharedPreferenceDelegates.int(defaultValue = 255, key = "translucent_background_alpha") var translucentBackgroundBlur by SharedPreferenceDelegates.int(key = "translucent_background_blur") diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/ThemeUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/ThemeUtil.java index d24ef04d..5d3d6b0e 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/ThemeUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/ThemeUtil.java @@ -17,7 +17,6 @@ import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringDef; import androidx.annotation.StyleRes; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -68,9 +67,6 @@ public class ThemeUtil { public static final String SP_CUSTOM_STATUS_BAR_FONT_DARK = "custom_status_bar_font_dark"; public static final String SP_CUSTOM_TOOLBAR_PRIMARY_COLOR = "custom_toolbar_primary_color"; - public static final String REASON_MANUALLY = "manually"; - public static final String REASON_FOLLOW_SYSTEM = "follow_system"; - public static final String REASON_TIME = "time"; public static final String SP_TRANSLUCENT_THEME_BACKGROUND_PATH = "translucent_theme_background_path"; public static int fixColorForTranslucentTheme(int color) { @@ -89,11 +85,7 @@ public class ThemeUtil { } public static void switchToNightMode(Activity context) { - switchToNightMode(context, REASON_MANUALLY); - } - - public static void switchToNightMode(Activity context, @Reason String reason) { - switchToNightMode(context, reason, true); + switchToNightMode(context, true); } public static void refreshUI(Activity activity) { @@ -105,10 +97,9 @@ public class ThemeUtil { } @SuppressLint("ApplySharedPref") - public static void switchToNightMode(Activity context, @Reason String reason, boolean recreate) { + public static void switchToNightMode(Activity context, boolean recreate) { getSharedPreferences(context) .edit() - .putString(SP_SWITCH_REASON, reason) .putString(SP_OLD_THEME, getTheme(context)) .putString(SP_THEME, getSharedPreferences(context).getString(SP_DARK_THEME, THEME_BLUE_DARK)) .commit(); @@ -119,28 +110,18 @@ public class ThemeUtil { @SuppressLint("ApplySharedPref") public static void switchFromNightMode(Activity context) { - switchFromNightMode(context, REASON_MANUALLY); + switchFromNightMode(context, true); } @SuppressLint("ApplySharedPref") public static void switchFromNightMode(Activity context, boolean recreate) { - switchFromNightMode(context, REASON_MANUALLY, recreate); - } - - @SuppressLint("ApplySharedPref") - public static void switchFromNightMode(Activity context, @Reason String reason) { - switchFromNightMode(context, reason, true); - } - - @SuppressLint("ApplySharedPref") - public static void switchFromNightMode(Activity context, @Reason String reason, boolean recreate) { getSharedPreferences(context) .edit() - .putString(SP_SWITCH_REASON, reason) .putString(SP_THEME, getSharedPreferences(context).getString(SP_OLD_THEME, ThemeUtil.THEME_WHITE)) .commit(); - //context.recreate(); - if (recreate) refreshUI(context); + if (recreate) { + refreshUI(context); + } } public static SharedPreferences getSharedPreferences(Context context) { @@ -394,8 +375,4 @@ public class ThemeUtil { return THEME_WHITE; } } - - @StringDef({REASON_MANUALLY, REASON_FOLLOW_SYSTEM, REASON_TIME}) - public @interface Reason { - } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80386da0..6ee490d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -451,4 +451,7 @@ 发贴 刷新 回到顶部 + 您已开启跟随系统夜间模式,手动切换日夜模式需要关闭跟随系统,确定要取消跟随吗? + 保持跟随 + 取消跟随