Application : Optimize Code, Improved Cold Start Time & Magic

This commit is contained in:
Mr. Dragon
2019-03-26 06:05:38 +05:30
parent 7e68149402
commit 0ad03ccc55
14 changed files with 166 additions and 105 deletions

View File

@@ -48,9 +48,14 @@ import com.aurora.store.utility.ThemeUtil;
import com.aurora.store.view.CustomViewPager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class AuroraActivity extends AppCompatActivity {
@@ -175,15 +180,22 @@ public class AuroraActivity extends AppCompatActivity {
private void setupViewPager() {
mViewPagerAdapter = new CustomViewPagerAdapter(getSupportFragmentManager());
mViewPagerAdapter.addFragment(0, new HomeFragment());
mViewPagerAdapter.addFragment(1, new InstalledFragment());
mViewPagerAdapter.addFragment(2, new UpdatesFragment());
mViewPagerAdapter.addFragment(3, new SearchFragment());
mViewPager.setPagingEnabled(false);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setOffscreenPageLimit(4);
mViewPager.setCurrentItem(fragmentPos, true);
mDisposable.add(Observable.just(
new HomeFragment(),
new InstalledFragment(),
new UpdatesFragment(),
new SearchFragment())
.zipWith(Observable.interval(16, TimeUnit.MILLISECONDS), (fragment, interval) -> fragment)
.doOnNext(fragment -> mViewPagerAdapter.addFragment(fragmentPos++, fragment))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> {
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setPagingEnabled(false);
mViewPager.setOffscreenPageLimit(2);
mViewPager.setCurrentItem(0, true);
})
.subscribe());
}
private void setupBottomNavigation() {

View File

@@ -58,7 +58,6 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import static com.aurora.store.utility.ContextUtil.runOnUiThread;
@@ -107,7 +106,6 @@ public class AccountsFragment extends BaseFragment implements BaseFragment.Event
private Context context;
private boolean isDummy = false;
private boolean isLoggedIn = false;
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
@Override
public void onAttach(@NotNull Context context) {
@@ -131,8 +129,7 @@ public class AccountsFragment extends BaseFragment implements BaseFragment.Event
@Override
public void onDestroy() {
super.onDestroy();
mCompositeDisposable.clear();
mCompositeDisposable.dispose();
disposable.clear();
}
@Override
@@ -271,7 +268,7 @@ public class AccountsFragment extends BaseFragment implements BaseFragment.Event
private void logInWithDummy() {
switchButtonState(true);
mCompositeDisposable.add(Observable.fromCallable(() ->
disposable.add(Observable.fromCallable(() ->
new PlayStoreApiAuthenticator(context).login())
.subscribeOn(Schedulers.io())
.doOnSubscribe(sub -> mProgressBar.setVisibility(View.VISIBLE))
@@ -296,7 +293,7 @@ public class AccountsFragment extends BaseFragment implements BaseFragment.Event
private void logInWithGoogle(String email, String password) {
switchButtonState(true);
mCompositeDisposable.add(Observable.fromCallable(() ->
disposable.add(Observable.fromCallable(() ->
new PlayStoreApiAuthenticator(context).login(email, password))
.subscribeOn(Schedulers.io())
.doOnSubscribe(sub -> mProgressBar.setVisibility(View.VISIBLE))
@@ -322,7 +319,7 @@ public class AccountsFragment extends BaseFragment implements BaseFragment.Event
}
private void getUserInfo() {
mCompositeDisposable.add(Observable.fromCallable(() ->
disposable.add(Observable.fromCallable(() ->
new UserProfiler(context).getUserProfile())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View File

@@ -52,7 +52,7 @@ public abstract class BaseFragment extends Fragment {
protected CustomAppListIterator iterator;
protected CompositeDisposable mDisposable = new CompositeDisposable();
protected CompositeDisposable disposable = new CompositeDisposable();
private Context context;
private EventListenerImpl eventListenerImpl;
@@ -76,7 +76,7 @@ public abstract class BaseFragment extends Fragment {
}
public void processException(Throwable e) {
mDisposable.clear();
disposable.clear();
Log.d("GoogleAPI DownloadRequest Failed : %s", e.getMessage());
if (e instanceof AuthException) {
processAuthException((AuthException) e);
@@ -124,7 +124,7 @@ public abstract class BaseFragment extends Fragment {
}
private void logInWithDummy() {
mDisposable.add(Observable.fromCallable(() ->
disposable.add(Observable.fromCallable(() ->
new PlayStoreApiAuthenticator(context).login())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
@@ -139,7 +139,7 @@ public abstract class BaseFragment extends Fragment {
}
private void refreshToken() {
mDisposable.add(Flowable.fromCallable(() ->
disposable.add(Flowable.fromCallable(() ->
new PlayStoreApiAuthenticator(context).refreshToken())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())

View File

@@ -28,6 +28,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import com.aurora.store.R;
@@ -42,7 +43,7 @@ import com.google.android.material.tabs.TabLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
public class CategoryAppsFragment extends BaseFragment implements BaseFragment.EventListenerImpl {
public class CategoryAppsFragment extends Fragment {
public static String categoryId;
@@ -103,19 +104,4 @@ public class CategoryAppsFragment extends BaseFragment implements BaseFragment.E
});
filterSheet.show(getChildFragmentManager(), "FILTER");
}
@Override
public void onLoggedIn() {
}
@Override
public void onLoginFailed() {
}
@Override
public void onNetworkFailed() {
}
}

View File

@@ -0,0 +1,26 @@
package com.aurora.store.fragment;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.aurora.store.SharedPreferencesTranslator;
import com.aurora.store.utility.Util;
import io.reactivex.disposables.CompositeDisposable;
public class ContainerFragment extends Fragment {
CompositeDisposable disposable = new CompositeDisposable();
SharedPreferencesTranslator translator;
Context context;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
translator = new SharedPreferencesTranslator(Util.getPrefs(context));
}
}

View File

@@ -57,6 +57,8 @@ import com.aurora.store.utility.Log;
import com.aurora.store.utility.PackageUtil;
import com.aurora.store.view.ErrorView;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
@@ -127,14 +129,14 @@ public class DetailsFragment extends BaseFragment implements BaseFragment.EventL
context.unregisterReceiver(mInstallReceiver);
mActionButton = null;
mTaskHelper = null;
mDisposable.clear();
disposable.clear();
} catch (Exception ignored) {
}
}
private void fetchDetails() {
mTaskHelper = new DetailsApp(getContext());
mDisposable.add(Observable.fromCallable(() -> mTaskHelper.getInfo(packageName))
disposable.add(Observable.fromCallable(() -> mTaskHelper.getInfo(packageName))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(app -> {
@@ -149,7 +151,7 @@ public class DetailsFragment extends BaseFragment implements BaseFragment.EventL
private void draw(App mApp) {
app = mApp;
drawButtons();
mDisposable.add(Observable.just(
disposable.add(Observable.just(
new GeneralDetails(this, app),
new Screenshot(this, app),
new Reviews(this, app),
@@ -160,7 +162,11 @@ public class DetailsFragment extends BaseFragment implements BaseFragment.EventL
new Share(this, app),
new SystemAppPage(this, app),
new Beta(this, app))
.subscribe(AbstractHelper::draw));
.zipWith(Observable.interval(16, TimeUnit.MILLISECONDS), (abstractHelper, interval) -> abstractHelper)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(AbstractHelper::draw)
.subscribe());
new ClusterDetails(this, app).draw();
}
@@ -200,7 +206,7 @@ public class DetailsFragment extends BaseFragment implements BaseFragment.EventL
@Override
public void processException(Throwable e) {
mDisposable.clear();
disposable.clear();
if (e instanceof MalformedRequestException) {
setErrorView(ErrorType.MALFORMED);
switchViews(true);

View File

@@ -36,10 +36,15 @@ import com.aurora.store.activity.CategoriesActivity;
import com.aurora.store.view.FeaturedAppsView;
import com.google.android.material.chip.Chip;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class ExploreFragment extends BaseFragment implements BaseFragment.EventListenerImpl {
public class ExploreFragment extends ContainerFragment {
@BindView(R.id.all_chip)
Chip allChip;
@@ -71,7 +76,6 @@ public class ExploreFragment extends BaseFragment implements BaseFragment.EventL
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
addViews();
allChip.setOnClickListener(v -> {
Intent intent = new Intent(context, CategoriesActivity.class);
intent.putExtra("INTENT_CATEGORY", "APPLICATION");
@@ -86,24 +90,24 @@ public class ExploreFragment extends BaseFragment implements BaseFragment.EventL
addViews();
}
@Override
public void onDestroy() {
super.onDestroy();
disposable.clear();
}
private void addViews() {
bulk_layout.addView(new FeaturedAppsView(context, "Personalization", "PERSONALIZATION"));
bulk_layout.addView(new FeaturedAppsView(context, "Communication", "COMMUNICATION"));
bulk_layout.addView(new FeaturedAppsView(context, "Tools", "TOOLS"));
}
@Override
public void onLoggedIn() {
bulk_layout.removeAllViews();
}
@Override
public void onLoginFailed() {
}
@Override
public void onNetworkFailed() {
disposable.add(Observable.just(
new FeaturedAppsView(context, translator.getString("PERSONALIZATION"), "PERSONALIZATION"),
new FeaturedAppsView(context, translator.getString("COMMUNICATION"), "COMMUNICATION"),
new FeaturedAppsView(context, translator.getString("TOOLS"), "TOOLS"))
.zipWith(Observable.interval(16, TimeUnit.MILLISECONDS),
(featuredAppsView, interval) -> featuredAppsView)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(featuredAppsView -> bulk_layout.addView(featuredAppsView))
.subscribe()
);
}
}

View File

@@ -36,10 +36,15 @@ import com.aurora.store.activity.CategoriesActivity;
import com.aurora.store.view.FeaturedAppsView;
import com.google.android.material.chip.Chip;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class FamilyFragment extends BaseFragment implements BaseFragment.EventListenerImpl {
public class FamilyFragment extends ContainerFragment {
@BindView(R.id.bulk_layout)
LinearLayout bulk_layout;
@@ -49,7 +54,7 @@ public class FamilyFragment extends BaseFragment implements BaseFragment.EventLi
private Context context;
@Override
public void onAttach(Context context) {
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
}
@@ -62,7 +67,8 @@ public class FamilyFragment extends BaseFragment implements BaseFragment.EventLi
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tab_family, container, false);
ButterKnife.bind(this, view);
return view;
@@ -71,9 +77,6 @@ public class FamilyFragment extends BaseFragment implements BaseFragment.EventLi
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bulk_layout.addView(new FeaturedAppsView(context, "Education", "FAMILY_EDUCATION"));
bulk_layout.addView(new FeaturedAppsView(context, "Brain Games", "FAMILY_BRAINGAMES"));
bulk_layout.addView(new FeaturedAppsView(context, "Creativity", "FAMILY_CREATE"));
allChip.setOnClickListener(v -> {
Intent intent = new Intent(context, CategoriesActivity.class);
intent.putExtra("INTENT_CATEGORY", "FAMILY");
@@ -82,17 +85,30 @@ public class FamilyFragment extends BaseFragment implements BaseFragment.EventLi
}
@Override
public void onLoggedIn() {
public void onResume() {
super.onResume();
if (bulk_layout.getChildCount() < 1)
addViews();
}
@Override
public void onLoginFailed() {
public void onDestroy() {
super.onDestroy();
disposable.clear();
}
@Override
public void onNetworkFailed() {
private void addViews() {
bulk_layout.removeAllViews();
disposable.add(Observable.just(
new FeaturedAppsView(context, translator.getString("FAMILY_EDUCATION"), "FAMILY_EDUCATION"),
new FeaturedAppsView(context, translator.getString("FAMILY_BRAINGAMES"), "FAMILY_BRAINGAMES"),
new FeaturedAppsView(context, translator.getString("FAMILY_CREATE"), "FAMILY_CREATE"))
.zipWith(Observable.interval(16, TimeUnit.MILLISECONDS),
(featuredAppsView, interval) -> featuredAppsView)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(featuredAppsView -> bulk_layout.addView(featuredAppsView))
.subscribe()
);
}
}

View File

@@ -36,10 +36,16 @@ import com.aurora.store.activity.CategoriesActivity;
import com.aurora.store.view.FeaturedAppsView;
import com.google.android.material.chip.Chip;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class GamesFragment extends BaseFragment implements BaseFragment.EventListenerImpl {
public class GamesFragment extends ContainerFragment {
@BindView(R.id.bulk_layout)
LinearLayout bulk_layout;
@@ -62,7 +68,8 @@ public class GamesFragment extends BaseFragment implements BaseFragment.EventLis
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tab_games, container, false);
ButterKnife.bind(this, view);
return view;
@@ -71,9 +78,6 @@ public class GamesFragment extends BaseFragment implements BaseFragment.EventLis
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bulk_layout.addView(new FeaturedAppsView(context, "Action", "GAME_ACTION"));
bulk_layout.addView(new FeaturedAppsView(context, "Adventure", "GAME_ADVENTURE"));
bulk_layout.addView(new FeaturedAppsView(context, "Puzzle", "GAME_PUZZLE"));
allChip.setOnClickListener(v -> {
Intent intent = new Intent(context, CategoriesActivity.class);
intent.putExtra("INTENT_CATEGORY", "GAME");
@@ -82,17 +86,30 @@ public class GamesFragment extends BaseFragment implements BaseFragment.EventLis
}
@Override
public void onLoggedIn() {
public void onResume() {
super.onResume();
if (bulk_layout.getChildCount() < 1)
addViews();
}
@Override
public void onLoginFailed() {
public void onDestroy() {
super.onDestroy();
disposable.clear();
}
@Override
public void onNetworkFailed() {
private void addViews() {
bulk_layout.removeAllViews();
disposable.add(Observable.just(
new FeaturedAppsView(context, translator.getString("GAME_ACTION"), "GAME_ACTION"),
new FeaturedAppsView(context, translator.getString("GAME_ADVENTURE"), "GAME_ADVENTURE"),
new FeaturedAppsView(context, translator.getString("GAME_PUZZLE"), "GAME_PUZZLE"))
.zipWith(Observable.interval(16, TimeUnit.MILLISECONDS),
(featuredAppsView, interval) -> featuredAppsView)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(featuredAppsView -> bulk_layout.addView(featuredAppsView))
.subscribe()
);
}
}

View File

@@ -54,11 +54,10 @@ public class HomeFragment extends Fragment {
ViewPager mViewPager;
private Context context;
private CategoryManager mCategoryManager;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private CompositeDisposable disposable = new CompositeDisposable();
@Override
public void onAttach(Context context) {
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
}
@@ -71,7 +70,8 @@ public class HomeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
ButterKnife.bind(this, view);
return view;
@@ -86,8 +86,8 @@ public class HomeFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
compositeDisposable.dispose();
disposable.clear();
disposable.dispose();
}
private void init() {
@@ -95,6 +95,7 @@ public class HomeFragment extends Fragment {
mTabAdapter.addFragment(new ExploreFragment(), getString(R.string.tab_explore));
mTabAdapter.addFragment(new GamesFragment(), getString(R.string.tab_games));
mTabAdapter.addFragment(new FamilyFragment(), getString(R.string.tab_family));
mViewPager.setAdapter(mTabAdapter);
mTabLayout.setupWithViewPager(mViewPager);
mTabLayout.setTabMode(Util.isTabScrollable(context)
@@ -102,7 +103,7 @@ public class HomeFragment extends Fragment {
: TabLayout.MODE_FIXED);
attachIconsToTabs();
mCategoryManager = new CategoryManager(getContext());
CategoryManager mCategoryManager = new CategoryManager(getContext());
if (mCategoryManager.categoryListEmpty())
getCategoriesFromAPI();
}
@@ -121,13 +122,11 @@ public class HomeFragment extends Fragment {
}
private void getCategoriesFromAPI() {
compositeDisposable.add(Observable.fromCallable(() -> new CategoryList(getContext()).getResult())
disposable.add(Observable.fromCallable(() -> new CategoryList(getContext())
.getResult())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((success) -> {
if (success) {
Log.i("CategoryList fetch completed");
}
.subscribe(success -> {
}, err -> Log.e(err.getMessage())));
}
}

View File

@@ -54,7 +54,6 @@ import com.google.android.material.switchmaterial.SwitchMaterial;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import butterknife.BindView;
@@ -138,7 +137,7 @@ public class InstalledFragment extends BaseFragment implements BaseFragment.Even
private void fetchData() {
InstalledApps mTaskHelper = new InstalledApps(context);
mDisposable.add(Observable.fromCallable(() -> mTaskHelper.getInstalledApps(switchSystem.isChecked()))
disposable.add(Observable.fromCallable(() -> mTaskHelper.getInstalledApps(switchSystem.isChecked()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> mSwipeRefreshLayout.setRefreshing(true))

View File

@@ -134,7 +134,7 @@ public class SearchAppsFragment extends BaseFragment implements BaseFragment.Eve
@Override
public void onDestroy() {
Glide.with(this).pauseAllRequests();
mDisposable.dispose();
disposable.dispose();
if (mBottomNavigationView != null)
ViewUtil.showBottomNav(mBottomNavigationView, true);
if (Util.filterSearchNonPersistent(context))
@@ -182,7 +182,7 @@ public class SearchAppsFragment extends BaseFragment implements BaseFragment.Eve
iterator.setEnableFilter(true);
iterator.setFilter(new Filter(getContext()).getFilterPreferences());
}
mDisposable.add(Observable.fromCallable(() -> mSearchTask.getSearchResults(iterator))
disposable.add(Observable.fromCallable(() -> mSearchTask.getSearchResults(iterator))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(appList -> {

View File

@@ -59,7 +59,6 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import butterknife.BindView;
@@ -141,7 +140,7 @@ public class UpdatesFragment extends BaseFragment implements BaseFragment.EventL
private void fetchData() {
UpdatableApps mTaskHelper = new UpdatableApps(context);
mDisposable.add(Observable.fromCallable(mTaskHelper::getUpdatableApps)
disposable.add(Observable.fromCallable(mTaskHelper::getUpdatableApps)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> customSwipeToRefresh.setRefreshing(true))
@@ -248,7 +247,7 @@ public class UpdatesFragment extends BaseFragment implements BaseFragment.EventL
}
private void updateAllApps() {
mDisposable.add(Observable.fromCallable(() -> new BulkDeliveryData(context)
disposable.add(Observable.fromCallable(() -> new BulkDeliveryData(context)
.getDeliveryData(updatableAppList))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View File

@@ -116,7 +116,7 @@ public class BlacklistFragment extends BaseFragment implements BlacklistAdapter.
private void loadAllApps() {
InstalledApps mTaskHelper = new InstalledApps(context);
mDisposable.add(Observable.fromCallable(() -> mTaskHelper.getInstalledApps(false))
disposable.add(Observable.fromCallable(() -> mTaskHelper.getInstalledApps(false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> customSwipeToRefresh.setRefreshing(true))